.github/workflows/create-docker-image.yml
... ...
@@ -17,6 +17,18 @@ jobs:
17 17
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
18 18
runs-on: ubuntu-latest
19 19
steps:
20
+ - name: Convert repo name to lowercase GHCR package
21
+ id: ghcr
22
+ env:
23
+ GITHUB_REPOSITORY: ${{ github.repository }}
24
+ run: |
25
+ # $GITHUB_REPOSITORY is in format owner/repo
26
+ OWNER=${GITHUB_REPOSITORY%%/*} # extract owner
27
+ REPO=${GITHUB_REPOSITORY##*/} # extract repo
28
+ OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]') # lowercase owner
29
+ REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]') # lowercase repo
30
+ PACKAGE="$OWNER_LOWER/$REPO_LOWER"
31
+ echo "PACKAGE=${PACKAGE}" >> $GITHUB_OUTPUT
20 32
- name: Checkout
21 33
uses: actions/checkout@v4
22 34
with:
... ...
@@ -50,7 +62,7 @@ jobs:
50 62
- name: Download release
51 63
shell: bash
52 64
run: |
53
- RELEASE_TAR_GZ_FILENAME=$( configuration/github-download-release-assets.sh ${{ secrets.GITHUB_TOKEN }} ${{ env.RELEASE_PREFIX }} )
65
+ RELEASE_TAR_GZ_FILENAME=$( configuration/github-download-release-assets.sh ${{ secrets.GITHUB_TOKEN }} ${{ env.RELEASE_PREFIX }} ${{ github.repository }} )
54 66
RELEASE=$( echo ${RELEASE_TAR_GZ_FILENAME} | sed -e 's/\.tar\.gz$//' )
55 67
if [ -n ${RELEASE_TAR_GZ_FILENAME} ]; then
56 68
mv ${RELEASE_TAR_GZ_FILENAME} docker/
... ...
@@ -60,7 +72,7 @@ jobs:
60 72
uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0
61 73
with:
62 74
build-args: RELEASE=${{ env.RELEASE }}
63
- tags: ghcr.io/sap/sailing-analytics:${{ env.RELEASE }}${{ env.BRANCH == 'main' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest' || env.BRANCH == 'docker-17' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest-17' || env.BRANCH == 'docker-21' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest-21' || env.BRANCH == 'docker-24' && github.event.inputs.release == '' && ',ghcr.io/sap/sailing-analytics:latest-24' || '' }}
75
+ tags: ghcr.io/${{steps.ghcr.outputs.PACKAGE}}:${{ env.RELEASE }}${{ env.BRANCH == 'main' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-17' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-17', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-21' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-21', steps.ghcr.outputs.PACKAGE) || env.BRANCH == 'docker-24' && github.event.inputs.release == '' && format(',ghcr.io/{0}:latest-24', steps.ghcr.outputs.PACKAGE) || '' }}
64 76
annotations: |
65 77
maintainer=axel.uhl@sap.com
66 78
index:org.opencontainers.image.title=Sailing Analytics
.github/workflows/merge-main-into-docker-17.yml
... ...
@@ -1,29 +0,0 @@
1
-name: Merge main branch into docker-24 after successful build
2
-on:
3
- workflow_run:
4
- workflows: [release]
5
- types: [completed]
6
- branches: [main]
7
- workflow_dispatch: {}
8
-jobs:
9
- merge-main-into-docker-24:
10
- permissions:
11
- contents: write
12
- if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
13
- runs-on: ubuntu-latest
14
- steps:
15
- - name: Checkout
16
- uses: actions/checkout@v4
17
- with:
18
- ref: docker-24
19
- fetch-depth: 0 # fetch the whole thing to make sure the histories merge
20
- - name: Merge main into docker-24
21
- uses: devmasx/merge-branch@854d3ac71ed1e9deb668e0074781b81fdd6e771f # v1.4.0
22
- env:
23
- GH_TOKEN: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }}
24
- with:
25
- type: now
26
- from_branch: main
27
- target_branch: docker-24
28
- message: Auto-merging main into docker-24 after successful release build
29
- github_token: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }}
.github/workflows/merge-main-into-docker-24.yml
... ...
@@ -0,0 +1,29 @@
1
+name: Merge main branch into docker-24 after successful build
2
+on:
3
+ workflow_run:
4
+ workflows: [release]
5
+ types: [completed]
6
+ branches: [main]
7
+ workflow_dispatch: {}
8
+jobs:
9
+ merge-main-into-docker-24:
10
+ permissions:
11
+ contents: write
12
+ if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v4
17
+ with:
18
+ ref: docker-24
19
+ fetch-depth: 0 # fetch the whole thing to make sure the histories merge
20
+ - name: Merge main into docker-24
21
+ uses: devmasx/merge-branch@854d3ac71ed1e9deb668e0074781b81fdd6e771f # v1.4.0
22
+ env:
23
+ GH_TOKEN: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }}
24
+ with:
25
+ type: now
26
+ from_branch: main
27
+ target_branch: docker-24
28
+ message: Auto-merging main into docker-24 after successful release build
29
+ github_token: ${{ secrets.REPO_TOKEN_FOR_MERGE_AND_PUSH }}
.github/workflows/release.yml
... ...
@@ -5,6 +5,7 @@ on:
5 5
- 'wiki/**'
6 6
- '.github/workflows/*'
7 7
- '**/*.md'
8
+ - 'README.md'
8 9
workflow_dispatch:
9 10
inputs:
10 11
skip_tests:
... ...
@@ -14,8 +15,8 @@ on:
14 15
description: Build with a local target platform (set to "true" to use local target platform)
15 16
default: 'false'
16 17
runner_cpus:
17
- description: Default is 16, but can be 4, 8, and 64 as well
18
- default: '16'
18
+ description: Empty means ubuntu-latest, usually 4 CPUs with little disk/memory, but if you have runners named ubuntu-latest-Xcpu, it can be 4, 8, 16, and 64 as well, using, e.g., ubuntu-latest-16cpu; default is ubuntu-latest
19
+ default: ''
19 20
firefox_version:
20 21
description: Version to use for Selenium tests; default is 137.0, but could, e.g., be "141.0" or "latest"
21 22
default: '140.0'
... ...
@@ -25,16 +26,36 @@ jobs:
25 26
permissions:
26 27
contents: write
27 28
checks: write
28
- runs-on: ubuntu-latest-${{ github.event.inputs.runner_cpus == '' && '16' || github.event.inputs.runner_cpus }}cpu
29
+ actions: read
30
+ runs-on: ${{ github.event.inputs.runner_cpus != '' && format('ubuntu-latest-{0}cpu', github.event.inputs.runner_cpus) || 'ubuntu-latest' }}
29 31
steps:
32
+ - name: Free Disk Space (Ubuntu)
33
+ uses: jlumbroso/free-disk-space@v1.3.1
34
+ with:
35
+ # this might remove tools that are actually needed,
36
+ # if set to "true" but frees about 6 GB
37
+ tool-cache: false
38
+ # all of these default to true, but feel free to set to
39
+ # "false" if necessary for your workflow
40
+ android: false
41
+ dotnet: true
42
+ haskell: true
43
+ large-packages: true
44
+ docker-images: false
45
+ swap-storage: true
30 46
- name: Allocate swap space
31 47
shell: bash
32 48
run: |
33
- sudo dd if=/dev/zero of=/mnt/large-swapfile bs=1G count=10
49
+ sudo dd if=/dev/zero of=/mnt/large-swapfile bs=1G count=30
34 50
sudo chmod 600 /mnt/large-swapfile
35 51
sudo mkswap /mnt/large-swapfile
36 52
sudo chown root:root /mnt/large-swapfile
37 53
sudo swapon /mnt/large-swapfile
54
+ - name: Create TMP space under /mnt/tmp
55
+ shell: bash
56
+ run: |
57
+ sudo mkdir /mnt/tmp
58
+ sudo chmod 777 /mnt/tmp
38 59
- name: Collect Workflow Telemetry
39 60
uses: catchpoint/workflow-telemetry-action@94c3c3d9567a0205de6da68a76c428ce4e769af1 # v2.0.0
40 61
with:
... ...
@@ -79,10 +100,18 @@ jobs:
79 100
ports: '5672:5672'
80 101
- shell: bash
81 102
env: # Or as an environment variable
82
- APP_PARAMETERS: "-Daws.region=eu-west-1 -Dgoogle.maps.authenticationparams=${{ secrets.GOOGLE_MAPS_AUTHENTICATIONPARAMS }} -Daws.s3.test.s3AccessId=${{ secrets.AWS_S3_TEST_S3ACCESSID }} -Daws.s3.test.s3AccessKey=${{ secrets.AWS_S3_TEST_S3ACCESSKEY }} -Dgeonames.org.usernames=${{ secrets.GEONAMES_ORG_USERNAMES }}"
103
+ TMP: /mnt/tmp
104
+ AWS_S3_TEST_S3ACCESSID: ${{ secrets.AWS_S3_TEST_S3ACCESSID }}
105
+ AWS_S3_TEST_S3ACCESSKEY: ${{ secrets.AWS_S3_TEST_S3ACCESSKEY }}
106
+ GEONAMES_ORG_USERNAMES: ${{ secrets.GEONAMES_ORG_USERNAMES }}
107
+ GOOGLE_MAPS_AUTHENTICATION_PARAMS: ${{ secrets.GOOGLE_MAPS_AUTHENTICATION_PARAMS }}
108
+ APP_PARAMETERS: "-Daws.region=eu-west-1"
83 109
JAVA8_HOME: ${{env.JAVA_HOME_8_X64}}
84 110
run: |
85
- ./configuration/buildAndUpdateProduct.sh -x ${{ github.event.inputs.runner_cpus == '' && '16' || github.event.inputs.runner_cpus }} ${{ github.event.inputs.skip_tests == 'true' && '-t' || '' }} ${{ github.event.inputs.local_target_platform == 'true' && '-v' || '' }} build 2>&1
111
+ ./configuration/buildAndUpdateProduct.sh -x ${{ github.event.inputs.runner_cpus == '' && '4' || github.event.inputs.runner_cpus }} ${{ github.event.inputs.skip_tests == 'true' && '-t' || '' }} ${{ github.event.inputs.local_target_platform == 'true' && '-v' || '' }} build 2>&1
112
+ - name: show disk stats
113
+ if: always()
114
+ run: df -h
86 115
- name: Upload build log
87 116
uses: actions/upload-artifact@v4
88 117
if: always()
... ...
@@ -156,7 +185,11 @@ jobs:
156 185
asset_name: release-notes.txt
157 186
asset_content_type: text/plain
158 187
- name: Trigger Hudson job
159
- if: always()
188
+ env:
189
+ HUDSON_JOB_USERNAME: ${{ secrets.HUDSON_JOB_USERNAME }}
190
+ HUDSON_JOB_PASSWORD: ${{ secrets.HUDSON_JOB_PASSWORD }}
191
+ HUDSON_JOB_TOKEN: ${{ secrets.HUDSON_JOB_TOKEN }}
192
+ if: ${{ always() && env.HUDSON_JOB_USERNAME != '' && env.HUDSON_JOB_PASSWORD != '' && env.HUDSON_JOB_TOKEN != '' }}
160 193
shell: bash
161 194
run: |
162 195
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
Gemfile.lock
... ...
@@ -70,7 +70,7 @@ GEM
70 70
faraday_middleware (1.2.1)
71 71
faraday (~> 1.0)
72 72
fastimage (2.4.0)
73
- fastlane (2.227.1)
73
+ fastlane (2.228.0)
74 74
CFPropertyList (>= 2.3, < 4.0.0)
75 75
addressable (>= 2.8, < 3.0.0)
76 76
artifactory (~> 3.0)
... ...
@@ -134,12 +134,12 @@ GEM
134 134
google-apis-core (>= 0.11.0, < 2.a)
135 135
google-apis-storage_v1 (0.31.0)
136 136
google-apis-core (>= 0.11.0, < 2.a)
137
- google-cloud-core (1.8.0)
137
+ google-cloud-core (1.7.1)
138 138
google-cloud-env (>= 1.0, < 3.a)
139 139
google-cloud-errors (~> 1.0)
140 140
google-cloud-env (1.6.0)
141 141
faraday (>= 0.17.3, < 3.0)
142
- google-cloud-errors (1.5.0)
142
+ google-cloud-errors (1.4.0)
143 143
google-cloud-storage (1.47.0)
144 144
addressable (~> 2.8)
145 145
digest-crc (~> 0.4)
... ...
@@ -176,14 +176,14 @@ GEM
176 176
optparse (0.6.0)
177 177
os (1.1.4)
178 178
plist (3.7.2)
179
- public_suffix (6.0.1)
179
+ public_suffix (5.1.1)
180 180
rake (13.2.1)
181 181
representable (3.2.0)
182 182
declarative (< 0.1.0)
183 183
trailblazer-option (>= 0.1.1, < 0.2.0)
184 184
uber (< 0.2.0)
185 185
retriable (3.1.2)
186
- rexml (3.4.1)
186
+ rexml (3.4.2)
187 187
rouge (3.28.0)
188 188
ruby2_keywords (0.0.5)
189 189
rubyzip (2.4.1)
... ...
@@ -231,4 +231,4 @@ DEPENDENCIES
231 231
json (> 0)
232 232
233 233
BUNDLED WITH
234
- 2.6.1
234
+ 2.4.21
Home.md
... ...
@@ -62,6 +62,7 @@ SAP is at the center of today’s technology revolution, developing innovations
62 62
* [[Creating an EC2 image from scratch|wiki/info/landscape/creating-ec2-image-from-scratch]]
63 63
* [[Upgrading an EC2 image|wiki/info/landscape/upgrading-ec2-image]]
64 64
* [[Creating a webserver EC2 image from scratch|wiki/info/landscape/creating-ec2-image-for-webserver-from-scratch]]
65
+ * [[Upgrading Operating System Across Landscape|wiki/info/landscape/operating-system-upgrade]]
65 66
* [[EC2 mail relaying vs. Amazon Simple E-Mail Service (SES)|wiki/info/landscape/mail-relaying]]
66 67
* [[Establishing support@sapsailing.com with AWS SES, SNS, and Lambda|wiki/info/landscape/support-email]]
67 68
* [[Creating an EC2 image for a MongoDB Replica Set from scratch|wiki/info/landscape/creating-ec2-mongodb-image-from-scratch]]
... ...
@@ -157,10 +158,8 @@ SAP is at the center of today’s technology revolution, developing innovations
157 158
* [[Monitoring Apache and RabbitMQ|wiki/misc/monitoring-apache-and-rabbitmq]]
158 159
159 160
## Projects
160
-* [[Analytics on a stick|wiki/projects/analytics-on-a-stick]]
161
-* [[Consolidating User Stores (bug 4006 / 4018)|wiki/projects/consolidating-user-stores]]
162
-* [[Cloud Infrastructure Orchestration|wiki/projects/cloud-orchestrator]]
163 161
* [[Management Console for Easier Administration|wiki/howto/development/management-console]]
162
+* [[Cloud Infrastructure Orchestration|wiki/projects/cloud-orchestrator]]
164 163
165 164
## Events and Planning
166 165
* [[Project Planning (bigger development)|wiki/events/planning]]
README.md
... ...
@@ -2,7 +2,7 @@
2 2
3 3
## About this Project
4 4
5
-The Sailing Analytics, formerly known as the "SAP Sailing Analytics," are a solution for portraying and analyzing sailing regattas, supporting training scenarios, and powering the vast archive at https://sapsailing.com. The solution consists of a cloud application with a web-based user interface, as well as three companion apps that integrate with the cloud application. This repository has the code for the cloud-based web application, and two of the three mobile apps (Buoy Pinger and Race Manager). The third companion app (Sail Insight) is found in [another repository](https://github.com/SailTracks/sailinsight).
5
+The Sailing Analytics, formerly known as the "SAP Sailing Analytics," are a solution for portraying and analyzing sailing regattas, supporting training scenarios, and powering the vast archive at https://sapsailing.com. The solution consists of a cloud application with a web-based user interface, as well as three companion apps that integrate with the cloud application. This repository has the code for the cloud-based web application, and two of the three mobile apps (Buoy Pinger and Race Manager). The third companion app (Sail Insight) is found in [another repository](https://github.com/SAP/sailing-analytics-sail-insight).
6 6
7 7
## Description
8 8
... ...
@@ -32,11 +32,11 @@ Based on the ``docker/docker-compose.yml`` definition you should end up with thr
32 32
33 33
Try a request to [``http://127.0.0.1:8888/index.html``](http://127.0.0.1:8888/index.html) or [``http://127.0.0.1:8888/gwt/status``](http://127.0.0.1:8888/gwt/status) to see if things worked.
34 34
35
-To use Java 17, use the ``docker-compose-17.yml`` file instead:
35
+To use Java 24, use the ``docker-compose-24.yml`` file instead:
36 36
37 37
```
38 38
cd docker
39
- docker-compose -f docker-compose-17.yml up
39
+ docker-compose -f docker-compose-24.yml up
40 40
```
41 41
42 42
## Requirements
... ...
@@ -74,12 +74,123 @@ See [here](https://www.sapsailing.com/gwt/Home.html#/imprint/:) for a list of co
74 74
75 75
## Building and Running
76 76
77
-This assumes you have completed the onboarding (see again [here](https://wiki.sapsailing.com/wiki/howto/onboarding)) successfully. To build, then invoke
77
+Builds usually run on [GitHub Actions](https://github.com/SAP/sailing-analytics/actions/workflows/release.yml) upon every push. A few repository secrets ensure that the build process has the permissions it needs. Pushes to the ``main``, ``docker-24`` and ``releases/*`` branches also publish a [release](https://github.com/SAP/sailing-analytics/releases) after a successful build.
78
+
79
+There are two options for building, detailed below; for both, you need to fulfill a few prerequisites.
80
+
81
+### Prerequisites
82
+
83
+If you have forked the repository and would like to run your own build, you will need the following prerequisites:
84
+
85
+#### Google Maps API Key
86
+
87
+Create or log on to your Google account and go to the [Google Cloud Console](https://console.cloud.google.com/apis/) and create an API key at least for the Maps JavaScript API. To later run the full product, while you're here, you can also create an API key for the YouTube Data API v3.
88
+
89
+#### AWS S3 Bucket for Upload Tests
90
+
91
+The build runs integration tests using the AWS API, requiring an access key with permission to upload to a bucket called ``sapsailing-automatic-upload-test``. Create that bucket in your AWS S3 environment. You can create an IAM user with CLI access and a permission policy that restricts write permissions to just that test bucket. You have to create the bucked in AWS region ``eu-west-1``. A permission policy for that user can look like this:
92
+
93
+```
94
+{
95
+ "Version": "2012-10-17",
96
+ "Statement": [
97
+ {
98
+ "Sid": "Stmt1422015883000",
99
+ "Effect": "Allow",
100
+ "Action": [
101
+ "s3:AbortMultipartUpload",
102
+ "s3:DeleteObject",
103
+ "s3:DeleteObjectVersion",
104
+ "s3:GetBucketAcl",
105
+ "s3:GetBucketCORS",
106
+ "s3:GetBucketLocation",
107
+ "s3:GetBucketLogging",
108
+ "s3:GetBucketNotification",
109
+ "s3:GetBucketPolicy",
110
+ "s3:GetBucketTagging",
111
+ "s3:GetBucketVersioning",
112
+ "s3:GetBucketWebsite",
113
+ "s3:GetLifecycleConfiguration",
114
+ "s3:GetObject",
115
+ "s3:GetObjectAcl",
116
+ "s3:GetObjectTorrent",
117
+ "s3:GetObjectVersion",
118
+ "s3:GetObjectVersionAcl",
119
+ "s3:GetObjectVersionTorrent",
120
+ "s3:ListAllMyBuckets",
121
+ "s3:ListBucket",
122
+ "s3:ListBucketMultipartUploads",
123
+ "s3:ListBucketVersions",
124
+ "s3:ListMultipartUploadParts",
125
+ "s3:PutBucketLogging",
126
+ "s3:PutBucketNotification",
127
+ "s3:PutBucketPolicy",
128
+ "s3:PutBucketTagging",
129
+ "s3:PutBucketVersioning",
130
+ "s3:PutBucketWebsite",
131
+ "s3:PutLifecycleConfiguration",
132
+ "s3:PutObject",
133
+ "s3:PutObjectAcl",
134
+ "s3:PutObjectVersionAcl",
135
+ "s3:RestoreObject"
136
+ ],
137
+ "Resource": [
138
+ "arn:aws:s3:::sapsailing-automatic-upload-test/*",
139
+ "arn:aws:s3:::sapsailing-automatic-upload-test"
140
+ ]
141
+ }
142
+ ]
143
+}
144
+```
145
+
146
+Create an access key for that user and note key ID and secret.
147
+
148
+#### Account(s) for geonames.org
149
+
150
+The build runs integration tests against [geonames.org](https://geonames.org). Use their login/sign-up form to create your user account and note its username.
151
+
152
+### Get the GitHub Actions build to work in your forked repository
153
+
154
+Assign the IDs and secrets from the prerequisites to repository secrets in your forked repository as follows:
155
+
156
+```
157
+AWS_S3_TEST_S3ACCESSID: {your-S3-test-bucket-upload-token-ID}
158
+AWS_S3_TEST_S3ACCESSKEY: {key-for-your-S3-token}
159
+GEONAMES_ORG_USERNAMES: {comma-separated-list-of-geonames.org-usernames}
160
+GOOGLE_MAPS_AUTHENTICATION_PARAMS: key={your-Google-Maps-API-key}
161
+```
162
+
163
+Then, manually trigger the ``release`` workflow with default options. You find the "Run workflow" drop-down in your forked repository under ``https://github.com/{your-github-user}/[your-repository-name}/actions/workflows/release.yml``.
164
+
165
+Release builds will trigger the ``create-docker-image`` workflow which will produce a Docker image of your release and publish it as a "ghcr" package in your repository. Note that package names are computed from the repository name by converting the latter to all lowercase characters. If you want to use your packages in the docker-compose configurations from the ``docker/`` folder, make sure to adjust the package name so it points to your own fork's package registry.
166
+
167
+If you want automatic validation of your changes to the ``main`` branch also for newer Java versions, you can use the ``merge-main-into-docker-24`` workflow. It requires another secret:
168
+
169
+```
170
+REPO_TOKEN_FOR_MERGE_AND_PUSH: {a-GitHub-token-enabled-for-push}
171
+```
172
+
173
+The workflow will launch automatically after a release has been performed for the ``main`` branch and will try to merge the latest ``main`` branch into ``docker-24`` and pushing the merge result if the merge was successful. This will then trigger a build for the ``docker-24`` branch which, if successul, will in turn produce a release and a docker image for use with Java 24.
174
+
175
+### Run a build locally
176
+
177
+This assumes you have completed the onboarding (see again [here](https://wiki.sapsailing.com/wiki/howto/onboarding)) successfully, including the Maven-specific parts such as having Maven installed (>= 3.9.x) and having a valid ``toolchains.xml`` file in your ~/.m2 folder. Furthermore, set and export the following environment variables:
178
+
179
+```
180
+export AWS_S3_TEST_S3ACCESSID={your-S3-test-bucket-upload-token-ID}
181
+export AWS_S3_TEST_S3ACCESSKEY={key-for-your-S3-token}
182
+export GEONAMES_ORG_USERNAMES={comma-separated-list-of-geonames.org-usernames}
183
+export GOOGLE_MAPS_AUTHENTICATION_PARAMS=key={your-Google-Maps-API-key}
184
+export JAVA8_HOME={location-of-your-JDK8}
185
+export JAVA_HOME={location-of-your-JDK17-or-newer}
186
+```
187
+
188
+To build, then invoke
78 189
79 190
```
80 191
configuration/buildAndUpdateProduct.sh build
81 192
```
82
-This will build the Android companion apps first, then the web application. If the build was successful you can install the product locally by invoking
193
+This will build the Android companion apps first, then the web application. If you lack a proper Android SDK set-up, consider using the ``-a`` option to skip building the Android apps. If the build was successful you can install the product locally by invoking
83 194
84 195
```
85 196
configuration/buildAndUpdateProduct.sh install [ -s <server-name> ]
... ...
@@ -99,9 +210,9 @@ Run the ``buildAndUpdateProduct.sh`` without any arguments to see the sub-comman
99 210
100 211
## Downloading, Installing and Running an Official Release
101 212
102
-You need to have Java 8 installed. Get one from [here](https://tools.eu1.hana.ondemand.com/#cloud). Either ensure that this JVM's ``java`` executable in on the ``PATH`` or set ``JAVA_HOME`` appropriately.
213
+You need to have Java 8 installed. Get one from, e.g., [here](https://tools.eu1.hana.ondemand.com/#cloud). Either ensure that this JVM's ``java`` executable in on the ``PATH`` or set ``JAVA_HOME`` appropriately.
103 214
104
-At [https://releases.sapsailing.com](https://releases.sapsailing.com) you find official product builds. To fetch and install one of them, make an empty directory, change into it and run the ``refreshInstance.sh`` command, e.g., like this:
215
+At [https://github.com/SAP/sailing-analytics/releases](https://github.com/SAP/sailing-analytics/releases) you find official product builds. To fetch and install one of them, make an empty directory, change into it and run the ``refreshInstance.sh`` command, e.g., like this:
105 216
```
106 217
mkdir sailinganalytics
107 218
cd sailinganalytics
... ...
@@ -113,7 +224,6 @@ This will download and install the latest release and configure it such that it
113 224
In addition to the necessary ``MONGODB_URI`` variable you may need to inject a few secrets into your runtime environment:
114 225
115 226
- ``MANAGE2SAIL_ACCESS_TOKEN`` access token for result and regatta structure import from the Manage2Sail regatta management system
116
-- ``IGTIMI_CLIENT_ID`` / ``IGTIMI_CLIENT_SECRET`` credentials for ``igtimi.com`` in case you have one or more WindBot devices that you would like to integrate with
117 227
- ``GOOGLE_MAPS_AUTHENTICATION_PARAMS`` as in ``"key=..."`` or ``"client=..."``, required to display the Google Map in the race viewer. Obtain a Google Maps key from the Google Cloud Developer console, e.g., [here](https://console.cloud.google.com/apis/dashboard).
118 228
- ``YOUTUBE_API_KEY`` as in ``"key=..."``, required to analyze time stamps and durations of YouTube videos when linking to races. Obtain a YouTube API key from the Google Cloud Developer console, e.g., [here](https://console.cloud.google.com/apis/dashboard).
119 229
REUSE.toml
... ...
@@ -2,6 +2,7 @@ version = 1
2 2
SPDX-PackageName = "sailing-analytics"
3 3
SPDX-PackageSupplier = "SAP Open Source Program Office ospo@sap.com"
4 4
SPDX-PackageDownloadLocation = "https://github.com/SAP/sailing-analytics"
5
+SPDX-PackageComment = "The code in this project may include calls to APIs (\"API Calls\") of\n SAP or third-party products or services developed outside of this project\n (\"External Products\").\n \"APIs\" means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project's code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls."
5 6
6 7
[[annotations]]
7 8
path = "java/com.google.gwt.servlet/**"
configuration/buildAndUpdateProduct.sh
... ...
@@ -222,7 +222,7 @@ echo TMP will be used for java.io.tmpdir and is $TMP
222 222
if [ "$TMP" = "" ]; then
223 223
export TMP=/tmp
224 224
fi
225
-extra="${extra} -Dgwt.workers=${GWT_WORKERS} -Djava.io.tmpdir=$TMP"
225
+extra="${extra} -Dgwt.workers=${GWT_WORKERS} -Djava.io.tmpdir=$TMP -Dgwt.workDir=$TMP"
226 226
extra="${extra} -Djdk.xml.maxGeneralEntitySizeLimit=0 -Djdk.xml.totalEntitySizeLimit=0"
227 227
228 228
shift $((OPTIND-1))
configuration/environments_scripts/repo/usr/local/bin/update_authorized_keys_for_landscape_managers_if_changed
... ...
@@ -28,7 +28,8 @@ if [ "${curl_exit_code}" = "0" ]; then
28 28
logger -t sailing "New SSH key changes for landscape managers (${last_change_millis} newer than ${PREVIOUS_CHANGE})"
29 29
if update_authorized_keys_for_landscape_managers "${BEARER_TOKEN}" "${BASE_URL}" "${LOGON_USER_HOME}" ; then
30 30
logger -t sailing "Updating SSH keys for landscape managers successful; updating ${LAST_CHANGE_FILE}"
31
- echo ${last_change_millis} >${LAST_CHANGE_FILE}
31
+ # /var/run is writable only for root, so we need to be able to sudo:
32
+ sudo bash -c "echo ${last_change_millis} >${LAST_CHANGE_FILE}"
32 33
else
33 34
logger -t sailing "Updating SSH keys for landscape managers failed with exit code $?; not updating ${LAST_CHANGE_FILE}"
34 35
fi
configuration/environments_scripts/reverse_proxy/setup-disposable-reverse-proxy.sh
... ...
@@ -1,6 +1,7 @@
1 1
#!/bin/bash
2 2
3
-# Setup script for Amazon Linux 2023. May need to update macro definitions for the archive IP.
3
+# Setup script for Amazon Linux 2023. Use 50g root partition.
4
+# May need to update macro definitions for the archive IP.
4 5
# Parameter 1 is the IP and parameter 2 is the bearer token to be installed in the root home dir.
5 6
# Ensure that the security for requesting the metadata uses IMDSv1
6 7
if [[ "$#" -ne 2 ]]; then
configuration/github-copy-release-to-sapsailing-com.sh
... ...
@@ -17,7 +17,7 @@
17 17
# produce false matches for the "main-" prefix.
18 18
BEARER_TOKEN="${1}"
19 19
RELEASE_NAME_PREFIX="${2}"
20
-RELEASE_TAR_GZ_FILE_NAME=$( `dirname "${0}"`/github-download-release-assets.sh "${BEARER_TOKEN}" "${RELEASE_NAME_PREFIX}" )
20
+RELEASE_TAR_GZ_FILE_NAME=$( `dirname "${0}"`/github-download-release-assets.sh "${BEARER_TOKEN}" "${RELEASE_NAME_PREFIX}" SAP/sailing-analytics )
21 21
if [ "${RELEASE_TAR_GZ_FILE_NAME}" != "" ]; then
22 22
RELEASE_NAME=$( echo ${RELEASE_TAR_GZ_FILE_NAME} | sed -e 's/^\(.*\)-\([0-9]*\).tar.gz$/\1/' )
23 23
RELEASE_TIMESTAMP=$( echo ${RELEASE_TAR_GZ_FILE_NAME} | sed -e 's/^\(.*\)-\([0-9]*\).tar.gz$/\2/' )
configuration/github-download-release-assets.sh
... ...
@@ -5,9 +5,9 @@
5 5
# always be 0.
6 6
#
7 7
# Usage:
8
-# ./github-download-release-assets.sh {BEARER_TOKEN} {release-name-prefix}
8
+# ./github-download-release-assets.sh {BEARER_TOKEN} {release-name-prefix} {repository-name}
9 9
# For example:
10
-# ./github-download-release-assets.sh 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 main-
10
+# ./github-download-release-assets.sh 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 main- SAP/sailing-analytics
11 11
# which will download the latest release tar.gz and release-notes.txt of the main branch (main-xxxxxxxxxxx).
12 12
# Note the "-" at the end of the "main-" prefix specifier; this way we're making name
13 13
# clashes with releases whose name happens to start with "main" unlikely. This
... ...
@@ -15,7 +15,8 @@
15 15
# produce false matches for the "main-" prefix.
16 16
BEARER_TOKEN="${1}"
17 17
RELEASE_NAME_PREFIX="${2}"
18
-RELEASES=$( curl -L -H 'Authorization: Bearer '${BEARER_TOKEN} https://api.github.com/repos/SAP/sailing-analytics/releases 2>/dev/null )
18
+GITHUB_REPOSITORY="${3}"
19
+RELEASES=$( curl -L -H 'Authorization: Bearer '${BEARER_TOKEN} https://api.github.com/repos/${GITHUB_REPOSITORY}/releases 2>/dev/null )
19 20
RELEASE_NOTES_TXT_ASSET_ID=$( echo "${RELEASES}" | jq -r 'sort_by(.published_at) | reverse | map(select(.name | startswith("'${RELEASE_NAME_PREFIX}'")))[0].assets[] | select(.content_type=="text/plain").id' 2>/dev/null)
20 21
if [ "$?" -ne "0" ]; then
21 22
echo "No release with prefix ${RELEASE_NAME_PREFIX} found. Not trying to download/upload anything." >&2
... ...
@@ -26,7 +27,7 @@ else
26 27
RELEASE_TIMESTAMP=$( echo ${RELEASE_FULL_NAME} | sed -e 's/^\(.*\)-\([0-9]*\)$/\2/' )
27 28
echo "Found release ${RELEASE_FULL_NAME} with name ${RELEASE_NAME} and time stamp ${RELEASE_TIMESTAMP}, notes ID is ${RELEASE_NOTES_TXT_ASSET_ID}, tarball ID is ${RELEASE_TAR_GZ_ASSET_ID}" >&2
28 29
RELEASE_TAR_GZ_FILE_NAME="${RELEASE_FULL_NAME}.tar.gz"
29
- curl -o "${RELEASE_TAR_GZ_FILE_NAME}" -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/SAP/sailing-analytics/releases/assets/'${RELEASE_TAR_GZ_ASSET_ID}
30
- curl -o release-notes.txt -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/SAP/sailing-analytics/releases/assets/'${RELEASE_NOTES_TXT_ASSET_ID}
30
+ curl -o "${RELEASE_TAR_GZ_FILE_NAME}" -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/'${GITHUB_REPOSITORY}'/releases/assets/'${RELEASE_TAR_GZ_ASSET_ID}
31
+ curl -o release-notes.txt -L -H 'Accept: application/octet-stream' -H 'Authorization: Bearer '${BEARER_TOKEN} 'https://api.github.com/repos/'${GITHUB_REPOSITORY}'/releases/assets/'${RELEASE_NOTES_TXT_ASSET_ID}
31 32
echo "${RELEASE_TAR_GZ_FILE_NAME}"
32 33
fi
configuration/github-download-workflow-artifacts.sh
... ...
@@ -12,12 +12,13 @@
12 12
# an exit status of 2 is returned.
13 13
BRANCH="${1}"
14 14
BEARER_TOKEN="${2}"
15
+GITHUB_REPOSITORY="${3}"
15 16
UNIX_TIME=$( date +%s )
16 17
UNIX_DATE=$( date --iso-8601=second )
17 18
UNIX_TIME_YESTERDAY=$(( UNIX_TIME - 10*24*3600 )) # look back ten days in time, trying to catch even re-runs of older jobs
18 19
DATE_YESTERDAY=$( date --iso-8601=second -d @${UNIX_TIME_YESTERDAY} )
19 20
HEADERS_FILE=$( mktemp headersXXXXX )
20
-NEXT_PAGE="https://api.github.com/repos/SAP/sailing-analytics/actions/runs?created=${DATE_YESTERDAY/+/%2B}..${UNIX_DATE/+/%2B}&per_page=100"
21
+NEXT_PAGE="https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs?created=${DATE_YESTERDAY/+/%2B}..${UNIX_DATE/+/%2B}&per_page=100"
21 22
ARTIFACTS_JSON=""
22 23
LATEST_RUN_STARTED_AT="0000-00-00T00:00:00Z"
23 24
# Now go through the pages as long as we have a non-empty NEXT_PAGE URL and find the completed "release" workflow that was started last
docker/makeImageForLatestRelease
... ...
@@ -1,5 +1,11 @@
1 1
#!/bin/bash
2
-release_prefix=$1
2
+release_prefix="$1"
3
+GITHUB_REPOSITORY="$2"
4
+OWNER=${GITHUB_REPOSITORY%%/*}
5
+OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]')
6
+REPO=${GITHUB_REPOSITORY##*/}
7
+REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]')
8
+PACKAGE="$OWNER_LOWER/$REPO_LOWER"
3 9
GITROOT="`dirname $0`/.."
4 10
DOCKERDIR="${GITROOT}/docker"
5 11
DOCKERFILE="$DOCKERDIR/Dockerfile"
... ...
@@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then
8 14
release_prefix="main-"
9 15
fi
10 16
pushd "${DOCKERDIR}"
11
-RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" )
17
+RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" )
12 18
if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then
13 19
echo "No release with prefix ${release_prefix} found" >&2
14 20
else
... ...
@@ -20,7 +26,7 @@ else
20 26
cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR"
21 27
cd "$DOCKERDIR"
22 28
docker buildx create --name=container --driver=docker-container --use --bootstrap
23
- docker buildx build --builder=container --platform=linux/amd64,linux/arm64 --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release} --push .
29
+ docker buildx build --builder=container --platform=linux/amd64,linux/arm64 --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release} --push .
24 30
echo "Cleaning up..."
25 31
rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt
26 32
fi
docker/makeImageForLatestRelease-arm
... ...
@@ -1,5 +1,11 @@
1 1
#!/bin/bash
2
-release_prefix=$1
2
+release_prefix="$1"
3
+GITHUB_REPOSITORY="$2"
4
+OWNER=${GITHUB_REPOSITORY%%/*}
5
+OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]')
6
+REPO=${GITHUB_REPOSITORY##*/}
7
+REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]')
8
+PACKAGE="$OWNER_LOWER/$REPO_LOWER"
3 9
GITROOT="`dirname $0`/.."
4 10
DOCKERDIR="${GITROOT}/docker"
5 11
DOCKERFILE="$DOCKERDIR/Dockerfile"
... ...
@@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then
8 14
release_prefix="main-"
9 15
fi
10 16
pushd "${DOCKERDIR}"
11
-RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" )
17
+RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" )
12 18
if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then
13 19
echo "No release with prefix ${release_prefix} found" >&2
14 20
else
... ...
@@ -19,13 +25,13 @@ else
19 25
cp "$GITROOT/java/target/start" "$DOCKERDIR"
20 26
cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR"
21 27
cd "$DOCKERDIR"
22
- docker build --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release}-arm .
28
+ docker build --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release}-arm .
23 29
echo "Cleaning up..."
24 30
rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt
25
- docker push ghcr.io/sap/sailing-analytics:${release}-arm
31
+ docker push ghcr.io/${PACKAGE}:${release}-arm
26 32
if [ "$SET_LATEST" = "1" ]; then
27
- docker tag ghcr.io/sap/sailing-analytics:${release}-arm ghcr.io/sap/sailing-analytics:latest-arm
28
- docker push ghcr.io/sap/sailing-analytics:latest-arm
33
+ docker tag ghcr.io/${PACKAGE}:${release}-arm ghcr.io/${PACKAGE}:latest-arm
34
+ docker push ghcr.io/${PACKAGE}:latest-arm
29 35
fi
30 36
fi
31 37
popd
docker/makeImageForLatestRelease-on-sapmachine11
... ...
@@ -1,5 +1,11 @@
1 1
#!/bin/bash
2
-release_prefix=$1
2
+release_prefix="$1"
3
+GITHUB_REPOSITORY="$2"
4
+OWNER=${GITHUB_REPOSITORY%%/*}
5
+OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]')
6
+REPO=${GITHUB_REPOSITORY##*/}
7
+REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]')
8
+PACKAGE="$OWNER_LOWER/$REPO_LOWER"
3 9
GITROOT="`dirname $0`/.."
4 10
DOCKERDIR="${GITROOT}/docker"
5 11
DOCKERFILE="$DOCKERDIR/Dockerfile"
... ...
@@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then
8 14
release_prefix="docker-11-"
9 15
fi
10 16
pushd "${DOCKERDIR}"
11
-RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" )
17
+RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" )
12 18
if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then
13 19
echo "No release with prefix ${release_prefix} found" >&2
14 20
else
... ...
@@ -19,13 +25,13 @@ else
19 25
cp "$GITROOT/java/target/start" "$DOCKERDIR"
20 26
cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR"
21 27
cd "$DOCKERDIR"
22
- docker build --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release} -f Dockerfile_sapsailing_on_sapmachine11 .
28
+ docker build --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release} -f Dockerfile_sapsailing_on_sapmachine11 .
23 29
echo "Cleaning up..."
24 30
rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt
25
- docker push ghcr.io/sap/sailing-analytics:${release}
31
+ docker push ghcr.io/${PACKAGE}:${release}
26 32
if [ "$SET_LATEST" = "1" ]; then
27
- docker tag ghcr.io/sap/sailing-analytics:${release} ghcr.io/sap/sailing-analytics:latest-11
28
- docker push ghcr.io/sap/sailing-analytics:latest-11
33
+ docker tag ghcr.io/${PACKAGE}:${release} ghcr.io/${PACKAGE}:latest-11
34
+ docker push ghcr.io/${PACKAGE}:latest-11
29 35
fi
30 36
fi
31 37
popd
docker/makeImageForLatestRelease-on-sapmachine17
... ...
@@ -1,5 +1,11 @@
1 1
#!/bin/bash
2
-release_prefix=$1
2
+release_prefix="$1"
3
+GITHUB_REPOSITORY="$2"
4
+OWNER=${GITHUB_REPOSITORY%%/*}
5
+OWNER_LOWER=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]')
6
+REPO=${GITHUB_REPOSITORY##*/}
7
+REPO_LOWER=$(echo "$REPO" | tr '[:upper:]' '[:lower:]')
8
+PACKAGE="$OWNER_LOWER/$REPO_LOWER"
3 9
GITROOT="`dirname $0`/.."
4 10
DOCKERDIR="${GITROOT}/docker"
5 11
DOCKERFILE="$DOCKERDIR/Dockerfile"
... ...
@@ -8,7 +14,7 @@ if [ "${release_prefix}" = "" ]; then
8 14
release_prefix="docker-17-"
9 15
fi
10 16
pushd "${DOCKERDIR}"
11
-RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" )
17
+RELEASE_TAR_GZ_FILENAME=$( ${GITROOT}/configuration/github-download-release-assets.sh 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 "${release_prefix}" "${GITHUB_REPOSITORY}" )
12 18
if [ "${RELEASE_TAR_GZ_FILENAME}" = "" ]; then
13 19
echo "No release with prefix ${release_prefix} found" >&2
14 20
else
... ...
@@ -19,13 +25,13 @@ else
19 25
cp "$GITROOT/java/target/start" "$DOCKERDIR"
20 26
cp "$GITROOT/java/target/configuration/JavaSE-11.profile" "$DOCKERDIR"
21 27
cd "$DOCKERDIR"
22
- docker build --build-arg RELEASE=${release} -t ghcr.io/sap/sailing-analytics:${release} -f Dockerfile_sapsailing_on_sapmachine17 .
28
+ docker build --build-arg RELEASE=${release} -t ghcr.io/${PACKAGE}:${release} -f Dockerfile_sapsailing_on_sapmachine17 .
23 29
echo "Cleaning up..."
24 30
rm start env.sh JavaSE-11.profile ${RELEASE_TAR_GZ_FILENAME} release-notes.txt
25
- docker push ghcr.io/sap/sailing-analytics:${release}
31
+ docker push ghcr.io/${PACKAGE}:${release}
26 32
if [ "$SET_LATEST" = "1" ]; then
27
- docker tag ghcr.io/sap/sailing-analytics:${release} ghcr.io/sap/sailing-analytics:latest-17
28
- docker push ghcr.io/sap/sailing-analytics:latest-17
33
+ docker tag ghcr.io/${PACKAGE}:${release} ghcr.io/${PACKAGE}:latest-17
34
+ docker push ghcr.io/${PACKAGE}:latest-17
29 35
fi
30 36
fi
31 37
popd
java/com.sap.sailing.aiagent/META-INF/MANIFEST.MF
... ...
@@ -10,6 +10,10 @@ Automatic-Module-Name: com.sap.sailing.aiagent
10 10
Import-Package: com.sap.sailing.aiagent.interfaces,
11 11
com.sap.sailing.domain.abstractlog.race,
12 12
com.sap.sailing.domain.abstractlog.regatta,
13
+ com.sap.sailing.domain.leaderboard,
14
+ com.sap.sailing.domain.tracking,
15
+ com.sap.sailing.domain.tracking.impl,
16
+ com.sap.sailing.domain.shared.tracking,
13 17
com.sap.sse.common,
14 18
com.sap.sse.concurrent,
15 19
com.sap.sse.security.shared,
java/com.sap.sailing.aiagent/src/com/sap/sailing/aiagent/impl/RaceListener.java
... ...
@@ -27,7 +27,7 @@ import com.sap.sailing.domain.common.tracking.GPSFix;
27 27
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
28 28
import com.sap.sailing.domain.common.tracking.SensorFix;
29 29
import com.sap.sailing.domain.leaderboard.Leaderboard;
30
-import com.sap.sailing.domain.tracking.AddResult;
30
+import com.sap.sailing.domain.shared.tracking.AddResult;
31 31
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
32 32
import com.sap.sailing.domain.tracking.MarkPassing;
33 33
import com.sap.sailing.domain.tracking.RaceChangeListener;
java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startanalysis/StartAnalysisDTOFactory.java
... ...
@@ -22,7 +22,7 @@ import com.sap.sailing.domain.common.NoWindException;
22 22
import com.sap.sailing.domain.common.Position;
23 23
import com.sap.sailing.domain.common.Wind;
24 24
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
25
-import com.sap.sailing.domain.tracking.LineDetails;
25
+import com.sap.sailing.domain.shared.tracking.LineDetails;
26 26
import com.sap.sailing.domain.tracking.MarkPassing;
27 27
import com.sap.sailing.domain.tracking.TrackedLeg;
28 28
import com.sap.sailing.domain.tracking.TrackedRace;
java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/StartlineAdvantageCalculationDataRetriever.java
... ...
@@ -15,8 +15,8 @@ import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
15 15
import com.sap.sailing.domain.common.polars.NotEnoughDataHasBeenAddedException;
16 16
import com.sap.sailing.domain.common.tracking.GPSFix;
17 17
import com.sap.sailing.domain.polars.PolarDataService;
18
+import com.sap.sailing.domain.shared.tracking.LineDetails;
18 19
import com.sap.sailing.domain.tracking.GPSFixTrack;
19
-import com.sap.sailing.domain.tracking.LineDetails;
20 20
import com.sap.sailing.domain.tracking.TrackedRace;
21 21
import com.sap.sse.common.Speed;
22 22
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java
... ...
@@ -29,15 +29,15 @@ import com.sap.sailing.domain.common.Tack;
29 29
import com.sap.sailing.domain.common.tracking.GPSFix;
30 30
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
31 31
import com.sap.sailing.domain.leaderboard.Leaderboard;
32
+import com.sap.sailing.domain.shared.tracking.LineDetails;
33
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
32 34
import com.sap.sailing.domain.tracking.GPSFixTrack;
33
-import com.sap.sailing.domain.tracking.LineDetails;
34 35
import com.sap.sailing.domain.tracking.Maneuver;
35 36
import com.sap.sailing.domain.tracking.MarkPassing;
36 37
import com.sap.sailing.domain.tracking.TrackedLeg;
37 38
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
38 39
import com.sap.sailing.domain.tracking.TrackedRace;
39 40
import com.sap.sailing.domain.tracking.WindPositionMode;
40
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
41 41
import com.sap.sse.common.Bearing;
42 42
import com.sap.sse.common.Distance;
43 43
import com.sap.sse.common.Duration;
java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java
... ...
@@ -34,8 +34,8 @@ import com.sap.sailing.domain.common.NoWindException;
34 34
import com.sap.sailing.domain.common.Position;
35 35
import com.sap.sailing.domain.common.WindSource;
36 36
import com.sap.sailing.domain.common.WindSourceType;
37
-import com.sap.sailing.domain.tracking.LineDetails;
38
-import com.sap.sailing.domain.tracking.Track;
37
+import com.sap.sailing.domain.shared.tracking.LineDetails;
38
+import com.sap.sailing.domain.shared.tracking.Track;
39 39
import com.sap.sailing.domain.tracking.TrackedLeg;
40 40
import com.sap.sailing.domain.tracking.TrackedRace;
41 41
import com.sap.sailing.geocoding.ReverseGeocoder;
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java
... ...
@@ -31,6 +31,7 @@ public enum BoatClassMasterdata {
31 31
BAVARIA_CRUISER_46 ("Bavaria Cruiser 46", true, 14.27, 4.35, BoatHullType.MONOHULL, true, "B46", "B 46", "BAVARIACRUISER46"),
32 32
BB10M ("BB 10m", true, 10.00, 2.30, BoatHullType.MONOHULL, true, "Dansk BB10M klub"),
33 33
BENETEAU_FIRST_35 ("Benetau First 35", true, 10.66, 3.636, BoatHullType.MONOHULL, true, "First 35"),
34
+ BENETEAU_FIRST_36 ("Benetau First 36", true, 11.98, 3.80, BoatHullType.MONOHULL, true, "First 36"),
34 35
BENETEAU_FIRST_45 ("Benetau First 45", true, 13.68, 4.202, BoatHullType.MONOHULL, true, "First 45"),
35 36
BRASSFAHRT_I ("Brassfahrt I", true, 12.00, 3.50, BoatHullType.MONOHULL, true, "Brassfahrt 1"),
36 37
BRASSFAHRT_II ("Brassfahrt II", true, 12.00, 3.50, BoatHullType.MONOHULL, true, "Brassfahrt 2"),
... ...
@@ -49,6 +50,7 @@ public enum BoatClassMasterdata {
49 50
DELPHIA_24 ("Delphia 24", true, 7.70, 2.50, BoatHullType.MONOHULL, true, "Delphia 24 One Design", "Delphia 24 OD"),
50 51
DYAS("Dyas", true, 7.15, 1.95, BoatHullType.MONOHULL, true),
51 52
ELAN350("Elan 350", true, 10.6, 3.5, BoatHullType.MONOHULL, true, "Elan 350 Performance"),
53
+ ELAN_E4("Elan E4", true, 10.6, 3.5, BoatHullType.MONOHULL, true, "Elan E4"),
52 54
EXTREME_40 ("Extreme 40", false, 12.2, 6.60, BoatHullType.CATAMARAN, true, "Extreme-40", "Extreme40", "ESS40", "ess"),
53 55
D_35 ("D35", false, 10.81, 6.89, BoatHullType.CATAMARAN, false),
54 56
ELLIOTT_6M ("Elliott 6m", true, 6.0, 2.35, BoatHullType.MONOHULL, true, "Elliott6m"),
java/com.sap.sailing.domain.igtimiadapter.persistence/src/com/sap/sailing/domain/igtimiadapter/persistence/impl/DomainObjectFactoryImpl.java
... ...
@@ -96,8 +96,8 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
96 96
final Document query = new Document();
97 97
appendMultiTimeRangeQuery(query, timeRanges);
98 98
query.append(FieldNames.IGTIMI_MESSAGES_DEVICE_SERIAL_NUMBER.name(), deviceSerialNumber);
99
- final Iterable<Document> queryResult = clientSessionOrNull == null ? messagesCollection.find(query)
100
- : messagesCollection.find(clientSessionOrNull, query);
99
+ final Iterable<Document> queryResult = (clientSessionOrNull == null ? messagesCollection.find(query)
100
+ : messagesCollection.find(clientSessionOrNull, query)).sort(Sorts.ascending(FieldNames.IGTIMI_MESSAGES_TIMESTAMP.name()));
101 101
result = Util.filter(Util.map(queryResult,
102 102
doc->{
103 103
try {
java/com.sap.sailing.domain.igtimiadapter.server/src/com/sap/sailing/domain/igtimiadapter/server/riot/RiotServer.java
... ...
@@ -53,9 +53,9 @@ import com.sap.sse.replication.Replicable;
53 53
* can be registered with this server using the {@link #addListener(BulkFixReceiver)} method.
54 54
* <p>
55 55
*
56
- * When combined with {@link IgtimiWindReceiver} (which is such a {@link BulkFixReceiver},
56
+ * When combined with {@link IgtimiWindReceiver} (which is such a {@link BulkFixReceiver}),
57 57
* {@link IgtimiWindListener}s can be registered on the wind receiver, just as they can
58
- * for a websocket connection.
58
+ * for a websocket connection.<p>
59 59
*
60 60
* The server uses <tt>java.nio</tt> and {@link ServerSocketChannel}s, avoiding the creation
61 61
* of a thread per connection.<p>
java/com.sap.sailing.domain.igtimiadapter.server/src/com/sap/sailing/domain/igtimiadapter/server/riot/impl/RiotServerImpl.java
... ...
@@ -337,24 +337,26 @@ public class RiotServerImpl extends AbstractReplicableWithObjectInputStream<Repl
337 337
// obtain the securityService only if connections exist; otherwise, we may still be in start-up mode
338 338
final SecurityService securityService = liveWebSocketConnections.isEmpty() ? null : getSecurityService();
339 339
for (final RiotWebsocketHandler webSocketClient : liveWebSocketConnections) {
340
- final User user = webSocketClient.getAuthenticatedUser();
341
- final OwnershipAnnotation deviceOwnership = securityService.getOwnership(device.getIdentifier());
342
- final AccessControlListAnnotation deviceAccessControlList = securityService.getAccessControlList(device.getIdentifier());
343
- if (!Util.isEmpty(Util.filter(daws, daw->{
344
- final OwnershipAnnotation dawOownership = securityService.getOwnership(daw.getIdentifier());
345
- final AccessControlListAnnotation dawAccessControlList = securityService.getAccessControlList(daw.getIdentifier());
346
- return PermissionChecker.isPermitted(
347
- daw.getIdentifier().getPermission(DefaultActions.READ),
340
+ if (webSocketClient.getDeviceSerialNumbers().contains(deviceSerialNumber)) {
341
+ final User user = webSocketClient.getAuthenticatedUser();
342
+ final OwnershipAnnotation deviceOwnership = securityService.getOwnership(device.getIdentifier());
343
+ final AccessControlListAnnotation deviceAccessControlList = securityService.getAccessControlList(device.getIdentifier());
344
+ if (!Util.isEmpty(Util.filter(daws, daw->{
345
+ final OwnershipAnnotation dawOownership = securityService.getOwnership(daw.getIdentifier());
346
+ final AccessControlListAnnotation dawAccessControlList = securityService.getAccessControlList(daw.getIdentifier());
347
+ return PermissionChecker.isPermitted(
348
+ daw.getIdentifier().getPermission(DefaultActions.READ),
349
+ user, securityService.getAllUser(),
350
+ dawOownership==null?null:dawOownership.getAnnotation(),
351
+ dawAccessControlList==null?null:dawAccessControlList.getAnnotation());
352
+ }))
353
+ && PermissionChecker.isPermitted(
354
+ device.getIdentifier().getPermission(DefaultActions.READ),
348 355
user, securityService.getAllUser(),
349
- dawOownership==null?null:dawOownership.getAnnotation(),
350
- dawAccessControlList==null?null:dawAccessControlList.getAnnotation());
351
- }))
352
- && PermissionChecker.isPermitted(
353
- device.getIdentifier().getPermission(DefaultActions.READ),
354
- user, securityService.getAllUser(),
355
- deviceOwnership==null?null:deviceOwnership.getAnnotation(),
356
- deviceAccessControlList==null?null:deviceAccessControlList.getAnnotation()) ) {
357
- webSocketClient.sendBytesByFuture(ByteBuffer.wrap(messageAsBytes));
356
+ deviceOwnership==null?null:deviceOwnership.getAnnotation(),
357
+ deviceAccessControlList==null?null:deviceAccessControlList.getAnnotation()) ) {
358
+ webSocketClient.sendBytesByFuture(ByteBuffer.wrap(messageAsBytes));
359
+ }
358 360
}
359 361
}
360 362
}
java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/IgtimiFixTrackTest.java
... ...
@@ -23,8 +23,8 @@ import com.sap.sailing.domain.igtimiadapter.datatypes.AWS;
23 23
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
24 24
import com.sap.sailing.domain.igtimiadapter.datatypes.HDGM;
25 25
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
26
+import com.sap.sailing.domain.shared.tracking.Track;
26 27
import com.sap.sailing.domain.tracking.DynamicTrack;
27
-import com.sap.sailing.domain.tracking.Track;
28 28
import com.sap.sse.common.TimePoint;
29 29
import com.sap.sse.common.Util;
30 30
import com.sap.sse.common.impl.DegreeBearingImpl;
java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnection.java
... ...
@@ -13,9 +13,9 @@ import org.json.simple.parser.ParseException;
13 13
import com.igtimi.IgtimiStream.Msg;
14 14
import com.sap.sailing.domain.igtimiadapter.datatypes.Fix;
15 15
import com.sap.sailing.domain.igtimiadapter.datatypes.Type;
16
+import com.sap.sailing.domain.shared.tracking.Track;
16 17
import com.sap.sailing.domain.tracking.DynamicTrack;
17 18
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
18
-import com.sap.sailing.domain.tracking.Track;
19 19
import com.sap.sailing.domain.tracking.TrackedRace;
20 20
import com.sap.sse.common.TimePoint;
21 21
import com.sap.sse.security.shared.HasPermissions;
java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/shared/IgtimiWindReceiver.java
... ...
@@ -188,8 +188,9 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
188 188
}
189 189
190 190
/**
191
- * Returns a wind fix produced out of an AWS/AWA and other fixes, as well as the fixes used for this, including a {@link BatteryLevel}
192
- * fix, if any was found, although this did technically not contribute to the production of the {@link Wind} object.
191
+ * Returns a wind fix produced out of an AWS/AWA and other fixes, as well as the fixes used for this (in case of
192
+ * interpolation between two fixes, the older one of the two is returned), including a {@link BatteryLevel} fix, if
193
+ * any was found, although this did technically not contribute to the production of the {@link Wind} object.
193 194
*/
194 195
private Pair<Wind, Set<Fix>> getWind(final TimePoint timePoint, String deviceSerialNumber) throws ClassNotFoundException, IOException, ParseException {
195 196
final Wind result;
... ...
@@ -213,7 +214,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
213 214
final SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection);
214 215
/*
215 216
* Hint from Brent Russell from Igtimi, at 2013-12-05 on the question whether to use GpsLatLong to
216
- * improve precision of boat speed / coarse over SOG/COG measurements:
217
+ * improve precision of boat speed / course over SOG/COG measurements:
217 218
*
218 219
* "Personally I would use COG/SOG exclusively, and if unhappy with the result add a small amount of
219 220
* smoothing and consider dropping samples as outliers if they cause a SOG discontinuity. The latter
... ...
@@ -260,7 +261,11 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
260 261
/**
261 262
* Searches for the last fix at or before {@code timePoint} in the given {@link trackToCleanUp}. If such a fix is
262 263
* found, adds the given fix to {@code fixesUsed} and clears all fixes from the {@code trackToCleanUp} that are
263
- * earlier than or at the {@code timePoint} given.
264
+ * earlier than the {@code timePoint} given. This in particular preserves at least one live fix which will "hide"
265
+ * older, e.g., buffered fixes that may be received when a WindBot device sends them out-of-order. For old, buffered
266
+ * fixes this may still mean that could be dropped prior to being converted into a {@link Wind} object with an older
267
+ * time stamp, but usually---and particularly with buffered data from previous days---this shouldn't be a problem.
268
+ * Also, old data can still be imported at a later point in time.
264 269
* <p>
265 270
*
266 271
* This assumes that the {@code fix} passed has been "consumed" now, and earlier fixes will not be relevant anymore.
... ...
@@ -270,7 +275,7 @@ public class IgtimiWindReceiver implements BulkFixReceiver {
270 275
final FixType fix = trackToCleanUp.getLastFixAtOrBefore(timePoint);
271 276
if (fix != null) {
272 277
fixesUsed.add(fix);
273
- trackToCleanUp.removeAllUpToAndIncluding(fix);
278
+ trackToCleanUp.removeAllUpToExcluding(fix);
274 279
}
275 280
}
276 281
java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java
... ...
@@ -45,8 +45,8 @@ import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServi
45 45
import com.sap.sailing.domain.racelogtracking.impl.SmartphoneImeiIdentifierImpl;
46 46
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
47 47
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
48
+import com.sap.sailing.domain.shared.tracking.Track;
48 49
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
49
-import com.sap.sailing.domain.tracking.Track;
50 50
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
51 51
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
52 52
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java
... ...
@@ -68,10 +68,10 @@ import com.sap.sailing.domain.racelogtracking.impl.SmartphoneImeiIdentifierImpl;
68 68
import com.sap.sailing.domain.racelogtracking.impl.fixtracker.RaceLogFixTrackerManager;
69 69
import com.sap.sailing.domain.racelogtracking.test.RaceLogTrackingTestHelper;
70 70
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
71
+import com.sap.sailing.domain.shared.tracking.Track;
71 72
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
72 73
import com.sap.sailing.domain.tracking.RaceHandle;
73 74
import com.sap.sailing.domain.tracking.RaceTrackingHandler.DefaultRaceTrackingHandler;
74
-import com.sap.sailing.domain.tracking.Track;
75 75
import com.sap.sailing.domain.tracking.TrackedRace;
76 76
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
77 77
import com.sap.sailing.server.impl.RacingEventServiceImpl;
java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java
... ...
@@ -88,13 +88,13 @@ import com.sap.sailing.domain.racelogtracking.impl.SmartphoneImeiIdentifierImpl;
88 88
import com.sap.sailing.domain.racelogtracking.impl.fixtracker.FixLoaderAndTracker;
89 89
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
90 90
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
91
+import com.sap.sailing.domain.shared.tracking.Track;
91 92
import com.sap.sailing.domain.tracking.BravoFixTrack;
92 93
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
93 94
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
94 95
import com.sap.sailing.domain.tracking.DynamicTrack;
95 96
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
96 97
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
97
-import com.sap.sailing.domain.tracking.Track;
98 98
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
99 99
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
100 100
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java
... ...
@@ -38,7 +38,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
38 38
import com.sap.sailing.domain.racelogtracking.impl.SmartphoneImeiIdentifierImpl;
39 39
import com.sap.sailing.domain.racelogtracking.impl.fixtracker.FixLoaderAndTracker;
40 40
import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest;
41
-import com.sap.sailing.domain.tracking.AddResult;
41
+import com.sap.sailing.domain.shared.tracking.AddResult;
42 42
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
43 43
import com.sap.sailing.domain.tracking.GPSTrackListener;
44 44
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java
... ...
@@ -64,6 +64,7 @@ import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry
64 64
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
65 65
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
66 66
import com.sap.sailing.domain.regattalike.IsRegattaLike;
67
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
67 68
import com.sap.sailing.domain.tracking.AbstractRaceTrackerBaseImpl;
68 69
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
69 70
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
... ...
@@ -73,7 +74,6 @@ import com.sap.sailing.domain.tracking.TrackedRace;
73 74
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
74 75
import com.sap.sailing.domain.tracking.WindStore;
75 76
import com.sap.sailing.domain.tracking.WindTrack;
76
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
77 77
import com.sap.sse.common.Util;
78 78
import com.sap.sse.common.Util.Pair;
79 79
import com.sap.sse.common.impl.MillisecondsTimePoint;
java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java
... ...
@@ -44,19 +44,19 @@ import com.sap.sailing.domain.racelog.tracking.SensorFixMapper;
44 44
import com.sap.sailing.domain.racelog.tracking.SensorFixStore;
45 45
import com.sap.sailing.domain.racelogsensortracking.SensorFixMapperFactory;
46 46
import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent;
47
+import com.sap.sailing.domain.shared.tracking.Track;
48
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
47 49
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
48 50
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
49 51
import com.sap.sailing.domain.tracking.DynamicTrack;
50 52
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
51 53
import com.sap.sailing.domain.tracking.Maneuver;
52 54
import com.sap.sailing.domain.tracking.RaceChangeListener;
53
-import com.sap.sailing.domain.tracking.Track;
54 55
import com.sap.sailing.domain.tracking.TrackedRace;
55 56
import com.sap.sailing.domain.tracking.TrackingDataLoader;
56 57
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
57 58
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
58 59
import com.sap.sailing.domain.tracking.impl.OutlierFilter;
59
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
60 60
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
61 61
import com.sap.sse.common.Duration;
62 62
import com.sap.sse.common.MultiTimeRange;
java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF
... ...
@@ -55,9 +55,9 @@ Export-Package: com.sap.sailing.domain.abstractlog,
55 55
com.sap.sailing.domain.racelogtracking,
56 56
com.sap.sailing.domain.racelogtracking.impl,
57 57
com.sap.sailing.domain.resultimport,
58
+ com.sap.sailing.domain.shared.tracking,
59
+ com.sap.sailing.domain.shared.tracking.impl,
58 60
com.sap.sailing.domain.statistics,
59
- com.sap.sailing.domain.statistics.impl,
60
- com.sap.sailing.domain.tracking,
61
- com.sap.sailing.domain.tracking.impl
61
+ com.sap.sailing.domain.statistics.impl
62 62
Automatic-Module-Name: com.sap.sailing.domain.shared.android
63 63
Import-Package: com.sap.sse.security.shared
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/AbstractLog.java
... ...
@@ -7,7 +7,7 @@ import java.util.NavigableSet;
7 7
import java.util.UUID;
8 8
9 9
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
10
-import com.sap.sailing.domain.tracking.Track;
10
+import com.sap.sailing.domain.shared.tracking.Track;
11 11
import com.sap.sse.common.TimePoint;
12 12
import com.sap.sse.common.WithID;
13 13
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/impl/AbstractLogImpl.java
... ...
@@ -24,9 +24,9 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
24 24
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEventComparator;
25 25
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
26 26
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
27
-import com.sap.sailing.domain.tracking.Track;
28
-import com.sap.sailing.domain.tracking.impl.PartialNavigableSetView;
29
-import com.sap.sailing.domain.tracking.impl.TrackImpl;
27
+import com.sap.sailing.domain.shared.tracking.Track;
28
+import com.sap.sailing.domain.shared.tracking.impl.PartialNavigableSetView;
29
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
30 30
import com.sap.sse.common.Timed;
31 31
import com.sap.sse.common.Util;
32 32
import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/impl/LogEventComparator.java
... ...
@@ -6,7 +6,7 @@ import java.util.Comparator;
6 6
import com.sap.sailing.domain.abstractlog.AbstractLogEvent;
7 7
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
8 8
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
9
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
9
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
10 10
import com.sap.sse.common.Timed;
11 11
import com.sap.sse.common.Util;
12 12
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/RaceLogAnalyzer.java
... ...
@@ -7,7 +7,7 @@ import com.sap.sailing.domain.abstractlog.impl.AbstractLogImpl;
7 7
import com.sap.sailing.domain.abstractlog.race.RaceLog;
8 8
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
9 9
import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
10
-import com.sap.sailing.domain.tracking.Track;
10
+import com.sap.sailing.domain.shared.tracking.Track;
11 11
12 12
public abstract class RaceLogAnalyzer<ResultType> extends BaseLogAnalyzer
13 13
<RaceLog, RaceLogEvent, RaceLogEventVisitor, ResultType> {
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/NoAddingRaceLogWrapper.java
... ...
@@ -16,7 +16,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog;
16 16
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
17 17
import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
18 18
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
19
-import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
19
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
20 20
import com.sap.sse.common.Duration;
21 21
import com.sap.sse.common.TimePoint;
22 22
import com.sap.sse.common.scalablevalue.ScalableValue;
... ...
@@ -273,8 +273,8 @@ public class NoAddingRaceLogWrapper implements RaceLog {
273 273
274 274
@Override
275 275
public <T> T getValueSum(TimePoint from, TimePoint to, T nullElement,
276
- com.sap.sailing.domain.tracking.Track.Adder<T> adder, TimeRangeCache<T> cache,
277
- com.sap.sailing.domain.tracking.Track.TimeRangeValueCalculator<T> valueCalculator) {
276
+ com.sap.sailing.domain.shared.tracking.Track.Adder<T> adder, TimeRangeCache<T> cache,
277
+ com.sap.sailing.domain.shared.tracking.Track.TimeRangeValueCalculator<T> valueCalculator) {
278 278
return innerRaceLog.getValueSum(from, to, nullElement, adder, cache, valueCalculator);
279 279
}
280 280
}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogImpl.java
... ...
@@ -8,8 +8,8 @@ import com.sap.sailing.domain.abstractlog.impl.AbstractLogImpl;
8 8
import com.sap.sailing.domain.abstractlog.race.RaceLog;
9 9
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
10 10
import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
11
-import com.sap.sailing.domain.tracking.Track;
12
-import com.sap.sailing.domain.tracking.impl.TrackImpl;
11
+import com.sap.sailing.domain.shared.tracking.Track;
12
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
13 13
14 14
/**
15 15
* {@link Track} implementation for {@link RaceLogEvent}s.
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java
... ...
@@ -5,7 +5,7 @@ import java.util.Locale;
5 5
import java.util.Map;
6 6
import java.util.Set;
7 7
8
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
8
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
9 9
import com.sap.sse.common.NamedWithID;
10 10
import com.sap.sse.common.Renamable;
11 11
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java
... ...
@@ -6,7 +6,7 @@ import java.util.UUID;
6 6
import com.sap.sailing.domain.base.EventBase;
7 7
import com.sap.sailing.domain.base.LeaderboardGroupBase;
8 8
import com.sap.sailing.domain.base.Venue;
9
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
9
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
10 10
import com.sap.sse.common.TimePoint;
11 11
12 12
/**
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/AddResult.java
... ...
@@ -0,0 +1,14 @@
1
+package com.sap.sailing.domain.shared.tracking;
2
+
3
+import javax.swing.plaf.basic.BasicSliderUI.TrackListener;
4
+
5
+/**
6
+ * The result of trying to add a fix to a {@link Track}. Used when notifying {@link TrackListener}s.
7
+ * This allows listeners, in particular, to distinguish between the add and replace scenario.
8
+ *
9
+ * @author Axel Uhl (D043530)
10
+ *
11
+ */
12
+public enum AddResult {
13
+ NOT_ADDED, ADDED, REPLACED;
14
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/FixAcceptancePredicate.java
... ...
@@ -0,0 +1,14 @@
1
+package com.sap.sailing.domain.shared.tracking;
2
+
3
+/**
4
+ * A predicate for a fix, for use in
5
+ * {@link Track#getInterpolatedValue(com.sap.sse.common.TimePoint, com.sap.sse.common.Util.Function)}, used, e.g., to
6
+ * provide a rule for when a fix shall be accepted during the search for surrounding fixes.
7
+ *
8
+ * @author Axel Uhl (d043530)
9
+ *
10
+ * @param <FixType>
11
+ */
12
+public interface FixAcceptancePredicate<FixType> {
13
+ boolean isAcceptFix(FixType fix);
14
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/LineDetails.java
... ...
@@ -0,0 +1,57 @@
1
+package com.sap.sailing.domain.shared.tracking;
2
+
3
+import com.sap.sailing.domain.base.Mark;
4
+import com.sap.sailing.domain.base.Waypoint;
5
+import com.sap.sailing.domain.common.NauticalSide;
6
+import com.sap.sailing.domain.common.Position;
7
+import com.sap.sse.common.Bearing;
8
+import com.sap.sse.common.Distance;
9
+import com.sap.sse.common.TimePoint;
10
+
11
+/**
12
+ * For a line such as a start or a finish line, tells the line's length at a given time, which side is
13
+ * {@link NauticalSide#PORT port} and which is {@link NauticalSide#STARBOARD starboard} when approaching the line,
14
+ * and---if wind information is available---its angle to a true wind direction and the advantageous side in approaching
15
+ * direction as well as how much the advantageous side is ahead. The wind-dependent information
16
+ * will all be <code>null</code> if no wind data is available.
17
+ *
18
+ * @author Axel Uhl (d043530)
19
+ *
20
+ */
21
+public interface LineDetails {
22
+ TimePoint getTimePoint();
23
+
24
+ Waypoint getWaypoint();
25
+
26
+ Distance getLength();
27
+
28
+ Bearing getAngleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind();
29
+
30
+ NauticalSide getAdvantageousSideWhileApproachingLine();
31
+
32
+ Mark getStarboardMarkWhileApproachingLine();
33
+
34
+ Mark getPortMarkWhileApproachingLine();
35
+
36
+ Distance getAdvantage();
37
+
38
+ Position getPortMarkPosition();
39
+
40
+ Position getStarboardMarkPosition();
41
+
42
+ default Mark getAdvantageousMark() {
43
+ return getAdvantageousSideWhileApproachingLine() == NauticalSide.PORT ? getPortMarkWhileApproachingLine() : getStarboardMarkWhileApproachingLine();
44
+ }
45
+
46
+ default Position getAdvantageousMarkPosition() {
47
+ return getAdvantageousSideWhileApproachingLine() == NauticalSide.PORT ? getPortMarkPosition() : getStarboardMarkPosition();
48
+ }
49
+
50
+ default Bearing getBearingFromStarboardToPortWhenApproachingLine() {
51
+ return getStarboardMarkPosition().getBearingGreatCircle(getPortMarkPosition());
52
+ }
53
+
54
+ default Bearing getBearingFromPortToStarboardWhenApproachingLine() {
55
+ return getPortMarkPosition().getBearingGreatCircle(getStarboardMarkPosition());
56
+ }
57
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/MappedTrack.java
... ...
@@ -0,0 +1,20 @@
1
+package com.sap.sailing.domain.shared.tracking;
2
+
3
+import com.sap.sse.common.Timed;
4
+
5
+/**
6
+ * {@link Track} specialization, which is mapped to a specific type of items.
7
+ *
8
+ * @param <ItemType>
9
+ * the type of item this track is mapped to
10
+ * @param <FixType>
11
+ * the type of fix that is contained in this track
12
+ */
13
+public interface MappedTrack<ItemType, FixType extends Timed> extends Track<FixType> {
14
+
15
+ /**
16
+ * @return the item this track is mapped to.
17
+ */
18
+ ItemType getTrackedItem();
19
+
20
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/Track.java
... ...
@@ -0,0 +1,259 @@
1
+package com.sap.sailing.domain.shared.tracking;
2
+
3
+import java.io.Serializable;
4
+import java.util.Iterator;
5
+import java.util.concurrent.locks.ReadWriteLock;
6
+import java.util.function.Function;
7
+
8
+import com.sap.sailing.domain.common.tracking.GPSFixMoving;
9
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
10
+import com.sap.sse.common.Duration;
11
+import com.sap.sse.common.TimePoint;
12
+import com.sap.sse.common.Timed;
13
+import com.sap.sse.common.scalablevalue.ScalableValue;
14
+
15
+/**
16
+ * A track records {@link Timed} items for an object of type <code>ItemType</code>. It allows clients to ask for a value
17
+ * close to a given {@link TimePoint}. The track manages a time-based set of raw fixes. An implementation may have an
18
+ * understanding of how to eliminate outliers. For example, if a track implementation knows it's tracking boats, it may
19
+ * consider fixes that the boat cannot possibly have reached due to its speed and direction change limitations as
20
+ * outliers. The set of fixes with outliers filtered out can be obtained using {@link #getFixes} whereas
21
+ * {@link #getRawFixes()} returns the unfiltered, raw fixes. If an implementation has no idea what an outlier is,
22
+ * both methods will return the same fix sequence.<p>
23
+ *
24
+ * With tracks, concurrency is an important issue. Threads may want to modify a track while other threads may want to
25
+ * read from it. Several methods such as {@link #getLastFixAtOrBefore(TimePoint)} return a single fix and can manage
26
+ * concurrency internally. However, those methods returning a collection of fixes, such as {@link #getFixes()} or an
27
+ * iterator over a collection of fixes, such as {@link #getFixesIterator(TimePoint, boolean)}, need special treatment.
28
+ * Until we internalize such iterations (see bug 824, http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=824),
29
+ * callers need to manage a read lock which is part of a {@link ReadWriteLock} managed by this track. Callers do so
30
+ * by calling {@link #lockForRead} and {@link #unlockAfterRead}.
31
+ *
32
+ * @author Axel Uhl (d043530)
33
+ */
34
+public interface Track<FixType extends Timed> extends Serializable {
35
+ /**
36
+ * An adding function to be used together with {@link Track#getValueSum(TimePoint, TimePoint, Object, Adder, TimeRangeCache, TimeRangeValueCalculator)}.
37
+ *
38
+ * @author Axel Uhl (D043530)
39
+ *
40
+ * @param <T>
41
+ */
42
+ static interface Adder<T> {
43
+ /**
44
+ * Adds two elements of type {@code T}. Neither argument must be {@code null}.
45
+ */
46
+ T add(T t1, T t2);
47
+ }
48
+
49
+ static interface TimeRangeValueCalculator<T> {
50
+ /**
51
+ * Calculates a value for fixes across a time range. When the method is called,
52
+ * a read lock will previously have been {@link Track#lockForRead obtained} before,
53
+ * so an implementing class does not need to worry about acquiring the lock.
54
+ */
55
+ T calculate(TimePoint from, TimePoint to);
56
+ }
57
+
58
+ /**
59
+ * Locks this track for reading by the calling thread. If the thread already holds the lock for this track,
60
+ * the hold count will be incremented. Make sure to call {@link #unlockAfterRead()} in a <code>finally</code>
61
+ * block to release the lock under all possible circumstances. Failure to do so will inevitably lead to
62
+ * deadlocks!
63
+ */
64
+ void lockForRead();
65
+
66
+ /**
67
+ * Decrements the hold count for this track's read lock for the calling thread. If it goes to zero, the lock will be
68
+ * released and other readers or a writer can obtain the lock. Make sure to call this method in a
69
+ * <code>finally</code> block for each {@link #lockForRead()} invocation.
70
+ */
71
+ void unlockAfterRead();
72
+
73
+ /**
74
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
75
+ * will be thrown in case the caller has failed to do so.
76
+ *
77
+ * @return the smoothened fixes
78
+ */
79
+ Iterable<FixType> getFixes();
80
+
81
+ /**
82
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
83
+ * will be thrown in case the caller has failed to do so.
84
+ *
85
+ * @return The smoothened fixes between from and to.
86
+ */
87
+ Iterable<FixType> getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive);
88
+
89
+ /**
90
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
91
+ * will be thrown in case the caller has failed to do so.
92
+ */
93
+ Iterable<FixType> getRawFixes();
94
+
95
+ /**
96
+ * Returns <code>null</code> if no such fix exists.
97
+ */
98
+ FixType getLastFixAtOrBefore(TimePoint timePoint);
99
+
100
+ /**
101
+ * Returns <code>null</code> if no such fix exists.
102
+ */
103
+ FixType getLastFixBefore(TimePoint timePoint);
104
+
105
+ /**
106
+ * Returns <code>null</code> if no such fix exists.
107
+ */
108
+ FixType getLastRawFixAtOrBefore(TimePoint timePoint);
109
+
110
+ /**
111
+ * Returns <code>null</code> if no such fix exists.
112
+ */
113
+ FixType getFirstFixAtOrAfter(TimePoint timePoint);
114
+
115
+ /**
116
+ * Returns <code>null</code> if no such fix exists.
117
+ */
118
+ FixType getFirstRawFixAtOrAfter(TimePoint timePoint);
119
+
120
+ /**
121
+ * Returns <code>null</code> if no such fix exists.
122
+ */
123
+ FixType getLastRawFixBefore(TimePoint timePoint);
124
+
125
+ /**
126
+ * Returns <code>null</code> if no such fix exists.
127
+ */
128
+ FixType getFirstRawFixAfter(TimePoint timePoint);
129
+
130
+ /**
131
+ * Returns <code>null</code> if no such fix exists.
132
+ */
133
+ FixType getFirstFixAfter(TimePoint timePoint);
134
+
135
+ /**
136
+ * The first fix in this track or <code>null</code> if the track is empty. The fix returned may
137
+ * be an outlier that is not returned by calls operating on the smoothened version of the track.
138
+ */
139
+ FixType getFirstRawFix();
140
+
141
+ /**
142
+ * The last fix in this track or <code>null</code> if the track is empty. The fix returned may
143
+ * be an outlier that is not returned by calls operating on the smoothened version of the track.
144
+ */
145
+ FixType getLastRawFix();
146
+
147
+ /**
148
+ * Interpolates an aspect of the fixes in this track for a given {@code timePoint}. If {@code timePoint} matches
149
+ * exactly a fix in this track, that fix is used. If this track is empty, {@code null} is returned. If the
150
+ * {@code timePoint} is after the last fix of this track, the last fix is used; if before the first fix, the first
151
+ * fix is used.
152
+ * <p>
153
+ *
154
+ * The fix(es) are converted to {@link ScalableValue}s using the {@code converter} which gives callers a choice
155
+ * which aspect of the fixes to project and interpolate. If more than one value results because two fixes (one
156
+ * before, one after) are used, linear interpolation based on the fixes' time points takes place.
157
+ * <p>
158
+ *
159
+ * Example: for a track of {@link GPSFixMoving} fixes the course over ground shall be determined for a given time
160
+ * point. The call would look like this:
161
+ * {@code getInterpolatedValue(timePoint, f->new ScalableBearing(f.getSpeed().getBearing()))}
162
+ *
163
+ * @return the projected interpolated value, typed by what the {@link ScalableValue#divide(double)} method returns.
164
+ */
165
+ <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
166
+ Function<FixType, ScalableValue<InternalType, ValueType>> converter);
167
+
168
+ /**
169
+ * Returns an iterator starting at the first fix after <code>startingAt</code> (or "at or after" in case
170
+ * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator exclude outliers (see
171
+ * also {@link #getFixes()} and returns the remaining fixes without any smoothening or dampening applied.
172
+ *
173
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
174
+ * will be thrown in case the caller has failed to do so.
175
+ */
176
+ Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive);
177
+
178
+ /**
179
+ * Returns an iterator starting at the first fix after <code>startingAt</code> (or "at or after" in case
180
+ * <code>inclusive</code> is <code>true</code>) and that ends at the <code>endingAt</code> time point or just before
181
+ * in case <code>endingAtIncluive</code> is false. The fixes returned by the iterator are the smoothened fixes (see
182
+ * also {@link #getFixes()}, without any smoothening or dampening applied.
183
+ *
184
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an
185
+ * exception will be thrown in case the caller has failed to do so.
186
+ *
187
+ * @param startingAt
188
+ * if <code>null</code>, starts with the first fix available
189
+ * @param endingAt
190
+ * if <code>null</code>., ends with the last fix available
191
+ */
192
+ Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt, boolean endingAtInclusive);
193
+
194
+ /**
195
+ * Returns an iterator starting at the first raw fix after <code>startingAt</code> (or "at or after" in case
196
+ * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the raw fixes (see also
197
+ * {@link #getRawFixes()}, without any smoothening or dampening applied.
198
+ *
199
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
200
+ * will be thrown in case the caller has failed to do so.
201
+ */
202
+ Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean inclusive);
203
+
204
+ /**
205
+ * Returns an iterator starting at the first raw fix after <code>startingAt</code> (or "at or after" in case
206
+ * <code>startingAtInclusive</code> is <code>true</code>) and ending at the <code>endingAt</code> time point or just before
207
+ * in case <code>endingAtIncluive</code> is false. The fixes returned by the iterator are the raw fixes (see also
208
+ * {@link #getRawFixes()}, without any smoothening or dampening applied.
209
+ *
210
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
211
+ * will be thrown in case the caller has failed to do so.
212
+ */
213
+ Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt, boolean endingAtInclusive);
214
+
215
+ /**
216
+ * Returns a descending iterator starting at the first fix before <code>startingAt</code> (or "at or before" in case
217
+ * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the smoothened fixes (see
218
+ * also {@link #getFixes()}, without any smoothening or dampening applied.
219
+ *
220
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
221
+ * will be thrown in case the caller has failed to do so.
222
+ */
223
+ Iterator<FixType> getFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
224
+
225
+ /**
226
+ * Returns a descending iterator starting at the first raw fix before <code>startingAt</code> (or "at or before" in case
227
+ * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the raw fixes (see also
228
+ * {@link #getRawFixes()}, without any smoothening or dampening applied.
229
+ *
230
+ * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
231
+ * will be thrown in case the caller has failed to do so.
232
+ */
233
+ Iterator<FixType> getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
234
+
235
+ /**
236
+ * @return the average duration between two fixes (outliers removed) in this track or <code>null</code> if there is not
237
+ * more than one fix in the track
238
+ */
239
+ Duration getAverageIntervalBetweenFixes();
240
+
241
+ /**
242
+ * @return the average duration between two fixes (outliers <em>not</em> removed) in this track or <code>null</code> if there is not
243
+ * more than one raw fix in the track
244
+ */
245
+ Duration getAverageIntervalBetweenRawFixes();
246
+
247
+ <T> T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator);
248
+
249
+ /**
250
+ * @return the number of raw fixes contained in the Track.
251
+ */
252
+ int size();
253
+
254
+ /**
255
+ * Tells whether the collection of {@link #getRawFixes() raw fixes} (no outliers removed) is empty
256
+ */
257
+ boolean isEmpty();
258
+
259
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/TrackingConnectorInfo.java
... ...
@@ -0,0 +1,28 @@
1
+
2
+package com.sap.sailing.domain.shared.tracking;
3
+
4
+import java.io.Serializable;
5
+
6
+/**
7
+ * Identifies the tracking connector that was used to create a TrackedRace.
8
+ * Further the Connector can provide a webUrl, that leads to an event web page.
9
+ */
10
+public interface TrackingConnectorInfo extends Serializable {
11
+
12
+ /**
13
+ * gets the name associated with the tracking technology used for the Race
14
+ */
15
+ String getTrackingConnectorName();
16
+
17
+ /**
18
+ * gets a {@link String} representation of the default web-URL associated with the tracking technology used for the Race.
19
+ * may be {@code null} if there is none provided in the adapter.
20
+ */
21
+ String getTrackingConnectorDefaultUrl();
22
+
23
+ /**
24
+ * gets a {@link String} representation of the web-URL associated with the Event.
25
+ * may be {@code null} if the API of the respective Tracking-Service does not provide a URL.
26
+ */
27
+ String getWebUrl();
28
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/LineDetailsImpl.java
... ...
@@ -0,0 +1,91 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import com.sap.sailing.domain.base.Mark;
4
+import com.sap.sailing.domain.base.Waypoint;
5
+import com.sap.sailing.domain.common.NauticalSide;
6
+import com.sap.sailing.domain.common.Position;
7
+import com.sap.sailing.domain.shared.tracking.LineDetails;
8
+import com.sap.sse.common.Bearing;
9
+import com.sap.sse.common.Distance;
10
+import com.sap.sse.common.TimePoint;
11
+
12
+public class LineDetailsImpl implements LineDetails {
13
+ private final TimePoint timePoint;
14
+ private final Waypoint waypoint;
15
+ private final Distance length;
16
+ private final Bearing angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
17
+ private final NauticalSide advantageousSidewhileApproachingLine;
18
+ private final Distance advantage;
19
+ private final Mark portMarkWhileApproachingLine;
20
+ private final Mark starboardMarkWhileApproachingLine;
21
+ private final Position portMarkPosition;
22
+ private final Position starboardMarkPosition;
23
+
24
+ public LineDetailsImpl(TimePoint timePoint, Waypoint waypoint, Distance length,
25
+ Bearing angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind, NauticalSide advantageousSideWhileApproachingLine,
26
+ Distance advantage, Mark portMarkWhileApproachingLine, Mark starboardMarkWhileApproachingLine,
27
+ Position portMarkPosition, Position starboardMarkPosition) {
28
+ super();
29
+ this.timePoint = timePoint;
30
+ this.waypoint = waypoint;
31
+ this.length = length;
32
+ this.angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind = angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
33
+ this.advantageousSidewhileApproachingLine = advantageousSideWhileApproachingLine;
34
+ this.advantage = advantage;
35
+ this.portMarkWhileApproachingLine = portMarkWhileApproachingLine;
36
+ this.starboardMarkWhileApproachingLine = starboardMarkWhileApproachingLine;
37
+ this.portMarkPosition = portMarkPosition;
38
+ this.starboardMarkPosition = starboardMarkPosition;
39
+ }
40
+
41
+ @Override
42
+ public TimePoint getTimePoint() {
43
+ return timePoint;
44
+ }
45
+
46
+ @Override
47
+ public Waypoint getWaypoint() {
48
+ return waypoint;
49
+ }
50
+
51
+ @Override
52
+ public Distance getLength() {
53
+ return length;
54
+ }
55
+
56
+ @Override
57
+ public Bearing getAngleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind() {
58
+ return angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
59
+ }
60
+
61
+ @Override
62
+ public NauticalSide getAdvantageousSideWhileApproachingLine() {
63
+ return advantageousSidewhileApproachingLine;
64
+ }
65
+
66
+ @Override
67
+ public Distance getAdvantage() {
68
+ return advantage;
69
+ }
70
+
71
+ @Override
72
+ public Mark getStarboardMarkWhileApproachingLine() {
73
+ return starboardMarkWhileApproachingLine;
74
+ }
75
+
76
+ @Override
77
+ public Mark getPortMarkWhileApproachingLine() {
78
+ return portMarkWhileApproachingLine;
79
+ }
80
+
81
+ @Override
82
+ public Position getPortMarkPosition() {
83
+ return portMarkPosition;
84
+ }
85
+
86
+ @Override
87
+ public Position getStarboardMarkPosition() {
88
+ return starboardMarkPosition;
89
+ }
90
+
91
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/MappedTrackImpl.java
... ...
@@ -0,0 +1,39 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import com.sap.sailing.domain.shared.tracking.MappedTrack;
4
+import com.sap.sse.common.Timed;
5
+import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
6
+
7
+/**
8
+ * Default implementation of {@link MappedTrack} interface.
9
+ *
10
+ * @param <ItemType>
11
+ * the type of item this track is mapped to
12
+ * @param <FixType>
13
+ * the type of fix that is contained in this track
14
+ */
15
+public class MappedTrackImpl<ItemType, FixType extends Timed> extends TrackImpl<FixType>
16
+ implements MappedTrack<ItemType, FixType> {
17
+
18
+ private static final long serialVersionUID = 6165693342087329096L;
19
+
20
+ private final ItemType trackedItem;
21
+
22
+ /** @see TrackImpl#TrackImpl(String) */
23
+ public MappedTrackImpl(ItemType trackedItem, String nameForReadWriteLock) {
24
+ super(nameForReadWriteLock);
25
+ this.trackedItem = trackedItem;
26
+ }
27
+
28
+ /** @see TrackImpl#TrackImpl(ArrayListNavigableSet, String) */
29
+ protected MappedTrackImpl(ItemType trackedItem, ArrayListNavigableSet<Timed> fixes, String nameForReadWriteLock) {
30
+ super(fixes, nameForReadWriteLock);
31
+ this.trackedItem = trackedItem;
32
+ }
33
+
34
+ @Override
35
+ public ItemType getTrackedItem() {
36
+ return trackedItem;
37
+ }
38
+
39
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/PartialNavigableSetView.java
... ...
@@ -0,0 +1,387 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import java.util.ArrayList;
4
+import java.util.Collection;
5
+import java.util.Comparator;
6
+import java.util.Iterator;
7
+import java.util.List;
8
+import java.util.NavigableSet;
9
+import java.util.NoSuchElementException;
10
+import java.util.SortedSet;
11
+import java.util.TreeSet;
12
+
13
+/**
14
+ * A view on a {@link NavigableSet} which suppresses some entries based on some configurable rule.
15
+ * The {@link #size()} operation is expensive because it requires a full scan. {@link #isEmpty()} is
16
+ * much cheaper because it suffices to find one element passing the filter rule. The filtering rule
17
+ * has to be expressed by subclsses implementing the {@link #isValid(Object)} method.
18
+ *
19
+ * @author Axel Uhl (d043530)
20
+ *
21
+ * @param <E>
22
+ */
23
+public abstract class PartialNavigableSetView<E> implements NavigableSet<E> {
24
+ private final NavigableSet<E> set;
25
+
26
+ private class PartialNavigableSetViewWithSameValidityAsEnclosing extends PartialNavigableSetView<E> {
27
+ public PartialNavigableSetViewWithSameValidityAsEnclosing(NavigableSet<E> set) {
28
+ super(set);
29
+ }
30
+
31
+ @Override
32
+ protected boolean isValid(E e) {
33
+ return PartialNavigableSetView.this.isValid(e);
34
+ }
35
+ }
36
+
37
+ private class FilteringIterator implements Iterator<E> {
38
+ /**
39
+ * The iterator is always kept one step "ahead" in order to know whether there really is a next element. The
40
+ * next valid element is fetched and stored in {@link #nextValid} and {@link #hasNext} is set to
41
+ * <code>true</code>.
42
+ */
43
+ private Iterator<E> iter;
44
+
45
+ private E nextValid;
46
+
47
+ private boolean hasNext;
48
+
49
+ private boolean hasLastNext;
50
+
51
+ private E lastNext;
52
+
53
+ public FilteringIterator() {
54
+ iter = getSet().iterator();
55
+ hasLastNext = false;
56
+ advance();
57
+ }
58
+
59
+ private void advance() {
60
+ if (iter.hasNext()) {
61
+ E next = iter.next();
62
+ while (!isValid(next) && iter.hasNext()) {
63
+ next = iter.next();
64
+ }
65
+ if (isValid(next)) {
66
+ nextValid = next;
67
+ hasNext = true;
68
+ } else {
69
+ hasNext = false;
70
+ }
71
+ } else {
72
+ hasNext = false;
73
+ }
74
+ }
75
+
76
+ @Override
77
+ public boolean hasNext() {
78
+ return hasNext;
79
+ }
80
+
81
+ @Override
82
+ public E next() {
83
+ if (hasNext) {
84
+ E result = nextValid;
85
+ advance();
86
+ hasLastNext = true;
87
+ lastNext = result;
88
+ return result;
89
+ } else {
90
+ throw new NoSuchElementException();
91
+ }
92
+ }
93
+
94
+ @Override
95
+ public void remove() {
96
+ if (!hasLastNext) {
97
+ throw new IllegalStateException("next() was not called before remove()");
98
+ } else {
99
+ PartialNavigableSetView.this.remove(lastNext);
100
+ hasLastNext = false;
101
+ }
102
+ }
103
+ }
104
+
105
+ public PartialNavigableSetView(NavigableSet<E> set) {
106
+ this.set = set;
107
+ }
108
+
109
+ /**
110
+ * Subclasses need to implement this method. For elements to be eliminated from the view represented by this
111
+ * object, return <code>false</code> for such an element being passed to this method.
112
+ */
113
+ abstract protected boolean isValid(E e);
114
+
115
+ @Override
116
+ public Comparator<? super E> comparator() {
117
+ return getSet().comparator();
118
+ }
119
+
120
+ public NavigableSet<E> descendingSet() {
121
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().descendingSet());
122
+ }
123
+
124
+ @Override
125
+ public Iterator<E> descendingIterator() {
126
+ return descendingSet().iterator();
127
+ }
128
+
129
+ @Override
130
+ public E first() {
131
+ E first = getSet().first();
132
+ while (first != null && !isValid(first)) {
133
+ first = getSet().higher(first);
134
+ }
135
+ if (first == null) {
136
+ throw new NoSuchElementException();
137
+ } else {
138
+ return first;
139
+ }
140
+ }
141
+
142
+ @Override
143
+ public E last() {
144
+ E last = getSet().last();
145
+ while (last != null && !isValid(last)) {
146
+ last = getSet().lower(last);
147
+ }
148
+ if (last == null) {
149
+ throw new NoSuchElementException();
150
+ } else {
151
+ return last;
152
+ }
153
+ }
154
+
155
+ @Override
156
+ public int size() {
157
+ int size = 0;
158
+ for (E e : getSet()) {
159
+ if (isValid(e)) {
160
+ size++;
161
+ }
162
+ }
163
+ return size;
164
+ }
165
+
166
+ @Override
167
+ public boolean isEmpty() {
168
+ for (E e : getSet()) {
169
+ if (isValid(e)) {
170
+ return false;
171
+ }
172
+ }
173
+ return true;
174
+ }
175
+
176
+ @SuppressWarnings("unchecked")
177
+ @Override
178
+ public boolean contains(Object o) {
179
+ return getSet().contains(o) && isValid((E) o);
180
+ }
181
+
182
+ @Override
183
+ public Object[] toArray() {
184
+ List<E> l = new ArrayList<E>();
185
+ for (E e : getSet()) {
186
+ if (isValid(e)) {
187
+ l.add(e);
188
+ }
189
+ }
190
+ return l.toArray();
191
+ }
192
+
193
+ @SuppressWarnings("unchecked")
194
+ @Override
195
+ public <T> T[] toArray(T[] a) {
196
+ List<T> l = new ArrayList<T>();
197
+ for (E e : getSet()) {
198
+ if (isValid(e)) {
199
+ l.add((T) e);
200
+ }
201
+ }
202
+ return l.toArray(a);
203
+
204
+ }
205
+
206
+ @Override
207
+ public boolean add(E e) {
208
+ return getSet().add(e);
209
+ }
210
+
211
+ @Override
212
+ public boolean remove(Object o) {
213
+ return getSet().remove(o);
214
+ }
215
+
216
+ @SuppressWarnings("unchecked")
217
+ @Override
218
+ public boolean containsAll(Collection<?> c) {
219
+ for (Object o : c) {
220
+ if (!isValid((E) o) || !getSet().contains(o)) {
221
+ return false;
222
+ }
223
+ }
224
+ return true;
225
+ }
226
+
227
+ @Override
228
+ public boolean addAll(Collection<? extends E> c) {
229
+ return getSet().addAll(c);
230
+ }
231
+
232
+ @Override
233
+ public boolean retainAll(Collection<?> c) {
234
+ return getSet().retainAll(c);
235
+ }
236
+
237
+ @Override
238
+ public boolean removeAll(Collection<?> c) {
239
+ return getSet().removeAll(c);
240
+ }
241
+
242
+ @Override
243
+ public void clear() {
244
+ getSet().clear();
245
+ }
246
+
247
+ @Override
248
+ public E lower(E e) {
249
+ E result = getSet().lower(e);
250
+ while (result != null && !isValid(result)) {
251
+ result = getSet().lower(result);
252
+ }
253
+ return result;
254
+ }
255
+
256
+ /**
257
+ * goes one left on the raw, unfiltered set and therefore may return fixes that have {@link #isValid(Object)}==
258
+ * <code>false</code>
259
+ */
260
+ protected E lowerInternal(E e) {
261
+ return getSet().lower(e);
262
+ }
263
+
264
+ /**
265
+ * goes one right on the raw, unfiltered set and therefore may return fixes that have {@link #isValid(Object)}==
266
+ * <code>false</code>
267
+ */
268
+ protected E higherInternal(E e) {
269
+ return getSet().higher(e);
270
+ }
271
+
272
+ @Override
273
+ public E floor(E e) {
274
+ E result = getSet().floor(e);
275
+ while (result != null && !isValid(result)) {
276
+ result = getSet().lower(result);
277
+ }
278
+ return result;
279
+ }
280
+
281
+ @Override
282
+ public E ceiling(E e) {
283
+ E result = getSet().ceiling(e);
284
+ while (result != null && !isValid(result)) {
285
+ result = getSet().higher(result);
286
+ }
287
+ return result;
288
+ }
289
+
290
+ @Override
291
+ public E higher(E e) {
292
+ E result = getSet().higher(e);
293
+ while (result != null && !isValid(result)) {
294
+ result = getSet().higher(result);
295
+ }
296
+ return result;
297
+ }
298
+
299
+ /**
300
+ * Removes all raw fixes that have {@link #isValid(Object)}==<code>false</code> and the first element to have
301
+ * {@link #isValid(Object)}==<code>true</code>. This latter element is returned. If no such element exists,
302
+ * <code>null</code> is returned. It is hence possible that invalid raw fixes are removed but stil <code>null</code>
303
+ * is returned.
304
+ */
305
+ @Override
306
+ public E pollFirst() {
307
+ E result = getSet().first();
308
+ while (result != null && !isValid(result)) {
309
+ getSet().remove(result);
310
+ result = getSet().first();
311
+ }
312
+ return result;
313
+ }
314
+
315
+ @Override
316
+ public E pollLast() {
317
+ E result = getSet().last();
318
+ while (result != null && !isValid(result)) {
319
+ getSet().remove(result);
320
+ result = getSet().last();
321
+ }
322
+ return result;
323
+ }
324
+
325
+ @Override
326
+ public NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) {
327
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().subSet(fromElement, fromInclusive, toElement, toInclusive));
328
+ }
329
+
330
+ @Override
331
+ public NavigableSet<E> headSet(E toElement, boolean inclusive) {
332
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().headSet(toElement, inclusive));
333
+ }
334
+
335
+ @Override
336
+ public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
337
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().tailSet(fromElement, inclusive));
338
+ }
339
+
340
+ @Override
341
+ public NavigableSet<E> subSet(E fromElement, E toElement) {
342
+ SortedSet<E> subSet = set.subSet(fromElement, toElement);
343
+ if (subSet instanceof NavigableSet<?>) {
344
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) subSet);
345
+ } else {
346
+ TreeSet<E> result = new TreeSet<E>(subSet);
347
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
348
+ }
349
+ }
350
+
351
+ @Override
352
+ public NavigableSet<E> headSet(E toElement) {
353
+ SortedSet<E> headSet = set.headSet(toElement);
354
+ if (headSet instanceof NavigableSet<?>) {
355
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) headSet);
356
+ } else {
357
+ TreeSet<E> result = new TreeSet<E>(headSet);
358
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
359
+ }
360
+ }
361
+
362
+ @Override
363
+ public NavigableSet<E> tailSet(E fromElement) {
364
+ SortedSet<E> tailSet = set.tailSet(fromElement);
365
+ if (tailSet instanceof NavigableSet<?>) {
366
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) tailSet);
367
+ } else {
368
+ TreeSet<E> result = new TreeSet<E>(tailSet);
369
+ return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
370
+ }
371
+ }
372
+
373
+
374
+ @Override
375
+ public Iterator<E> iterator() {
376
+ return new FilteringIterator();
377
+ }
378
+
379
+ @Override
380
+ public String toString() {
381
+ return new ArrayList<E>(this).toString();
382
+ }
383
+
384
+ protected NavigableSet<E> getSet() {
385
+ return set;
386
+ }
387
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/TimeRangeCache.java
... ...
@@ -0,0 +1,221 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import java.util.Comparator;
4
+import java.util.Iterator;
5
+import java.util.LinkedHashMap;
6
+import java.util.Map.Entry;
7
+import java.util.NavigableSet;
8
+
9
+import com.sap.sse.common.TimePoint;
10
+import com.sap.sse.common.Util;
11
+import com.sap.sse.common.Util.Pair;
12
+import com.sap.sse.concurrent.LockUtil;
13
+import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
14
+import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
15
+
16
+/**
17
+ * This cache looks "backwards." It contains pairs whose first component represents a <code>to</code> parameter used in
18
+ * a calculation for a time range. It is ordered by this component. The second component is a navigable, ordered set of
19
+ * pairs where the first pair component represents a <code>from</code> parameter used in the calculation's time range
20
+ * and the second pair component represents the result of the calculation for this parameter combination.
21
+ * <p>
22
+ *
23
+ * For implementation efficiency in combination with using an {@link ArrayListNavigableSet} for the values and in order
24
+ * to be able to efficiently extend a cache entry for a single <code>to</code> fix, the navigable sets containing the
25
+ * <code>from</code> fixes and results are ordered such that earlier fixes come later in the set. This way, extending
26
+ * the cache entry for a <code>to</code> fix to an earlier <code>from</code> fix only requires appending to the set.
27
+ * <p>
28
+ *
29
+ * <b>Invalidation</b>: When a new fix is added to the track, all cache entries for fixes at or later than the new fix's
30
+ * time point are removed from this cache. Additionally, the fix insertion may have an impact on the
31
+ * {@link #getEarliestFromAndResultAtOrAfterFrom(TimePoint, TimePoint) previous fix's} validity (track smoothing) and
32
+ * therefore on its selection for result aggregation. Therefore, if fix addition turned the previous fix invalid, the
33
+ * cache entries for the time points at or after the previous fix also need to be removed.
34
+ * <p>
35
+ *
36
+ * <b>Cache use</b>: When a result across a time range is to be computed the calculating method should first look for a
37
+ * cache entry for the <code>to</code> parameter. If one is found, the earliest entry in the navigable set for the
38
+ * navigable set of <code>from</code> and result values that is at or after the requested <code>from</code> time point
39
+ * is determined. If such an entry exists, the result is remembered and the algorithm is repeated recursively, using the
40
+ * <code>from</code> value found in the cache as the new <code>to</code> value, and the <code>from</code> value
41
+ * originally passed to the calculating method as <code>from</code> again. If no entry is found in the cache entry for
42
+ * <code>to</code> that is at or after the requested <code>from</code> time, the result has to be computed "from
43
+ * scratch."
44
+ * <p>
45
+ *
46
+ * If a cache entry for <code>to</code> is not found, the latest cache entry before it is looked up. If one is found,
47
+ * the result for the time range between the <code>to</code> time point requested and the <code>to</code> time point
48
+ * found in the cache is computed by iterating the smoothened fixes for this interval. If none is found, the result is
49
+ * computed by iterating backwards all the way to <code>from</code>.
50
+ * <p>
51
+ *
52
+ * Once the calculating method has computed its value, it should {@link #cache(TimePoint, TimePoint, Object) add} the
53
+ * result to the cache.
54
+ *
55
+ * @author Axel Uhl (D043530)
56
+ */
57
+public class TimeRangeCache<T> {
58
+ public static final int MAX_SIZE = 100;
59
+
60
+ private final NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> timeRangeCache;
61
+
62
+ /**
63
+ * The cache is to have limited size. Eviction shall happen based on a least-recently-used strategy. Usage is
64
+ * defined as having been returned by {@link #getEarliestFromAndResultAtOrAfterFrom(TimePoint, TimePoint)} or
65
+ * having been added by {@link #cache(TimePoint, TimePoint, Object)}.
66
+ * <p>
67
+ *
68
+ * When an eldest entry is asked to be expunged from this map and the map has more than {@link #MAX_SIZE} elements,
69
+ * the expunging will be admitted, and the entry is removed from the {@link #timeRangeCache} core structure. Reading
70
+ * and writing this structure must happen under the {@link #lock write lock} because also reading the linked hash
71
+ * map that counts access as "use" has a modifying effect on its internal structures.
72
+ * <p>
73
+ *
74
+ * The key pairs are from/to pairs. Note that this is in some sense "the opposite direction" compared to the
75
+ * alignment of the {@link #timeRangeCache} structure which has as its outer keys the "to" time point.<p>
76
+ *
77
+ * Read access is to be <code>synchronized<code> using this field's mutex; write access only happens under the
78
+ * {@link #lock write lock} and therefore will have no contenders.
79
+ */
80
+ private final LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void> lruCache;
81
+
82
+ private final NamedReentrantReadWriteLock lock;
83
+
84
+ private static final Comparator<Util.Pair<TimePoint, ?>> timePointInPairComparator = new Comparator<Util.Pair<TimePoint, ?>>() {
85
+ @Override
86
+ public int compare(Util.Pair<TimePoint, ?> o1, Util.Pair<TimePoint, ?> o2) {
87
+ return o1.getA().compareTo(o2.getA());
88
+ }
89
+ };
90
+
91
+ public TimeRangeCache(String nameForLockLogging) {
92
+ lock = new NamedReentrantReadWriteLock("lock for TimeRangeCache for "+nameForLockLogging, /* fair */ true);
93
+ this.timeRangeCache = new ArrayListNavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>>(timePointInPairComparator);
94
+ this.lruCache = new LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void>(/* initial capacity */ 10, /* load factor */ 0.75f,
95
+ /* access-based ordering */ true) {
96
+ private static final long serialVersionUID = -6568235517111733193L;
97
+
98
+ @Override
99
+ protected boolean removeEldestEntry(Entry<Pair<TimePoint, TimePoint>, Void> eldest) {
100
+ final boolean expunge = size() > MAX_SIZE;
101
+ if (expunge) {
102
+ removeCacheEntry(eldest.getKey().getA(), eldest.getKey().getB());
103
+ }
104
+ return expunge;
105
+ }
106
+ };
107
+ }
108
+
109
+ public int size() {
110
+ return lruCache.size();
111
+ }
112
+
113
+ private void removeCacheEntry(TimePoint from, TimePoint to) {
114
+ assert lock.getWriteHoldCount() == 1; // we can be sure we are alone here; this only happens when adding a new entry, holding the write lock
115
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(createDummy(to));
116
+ if (entryForTo.getA().equals(to)) {
117
+ Pair<TimePoint, T> entryForFrom = entryForTo.getB().ceiling(new Util.Pair<TimePoint, T>(from, null));
118
+ if (entryForFrom.getA().equals(from)) {
119
+ entryForTo.getB().remove(entryForFrom);
120
+ if (entryForTo.getB().isEmpty()) {
121
+ timeRangeCache.remove(entryForTo);
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Looks up the entry closest to but no later than <code>to</code>. If not found, <code>null</code> is returned. If
129
+ * found, the earliest pair of from/result that is at or after <code>from</code> will be returned, together with
130
+ * the <code>to</code> value of the entry. If there is no entry that is at or after <code>from</code>,
131
+ * <code>null</code> is returned.
132
+ */
133
+ public Util.Pair<TimePoint, Util.Pair<TimePoint, T>> getEarliestFromAndResultAtOrAfterFrom(TimePoint from, TimePoint to) {
134
+ LockUtil.lockForRead(lock);
135
+ try {
136
+ Util.Pair<TimePoint, Util.Pair<TimePoint, T>> result = null;
137
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(createDummy(to));
138
+ if (entryForTo != null) {
139
+ final Util.Pair<TimePoint, T> fromCeiling = entryForTo.getB().ceiling(new Util.Pair<TimePoint, T>(from, null));
140
+ if (fromCeiling != null) {
141
+ result = new Util.Pair<TimePoint, Util.Pair<TimePoint, T>>(entryForTo.getA(), fromCeiling);
142
+ }
143
+ }
144
+ // no writer can be active because we're holding the read lock; read access on the lruCache is synchronized using
145
+ // the lruCache's mutex; this is necessary because we're using access-based LRU pinging where even getting an entry
146
+ // modifies the internal parts of the data structure which is not thread safe.
147
+ synchronized (lruCache) { // ping the "perfect match" although it may not even have existed in the cache
148
+ lruCache.get(new Util.Pair<TimePoint, TimePoint>(from, to));
149
+ }
150
+ return result;
151
+ } finally {
152
+ LockUtil.unlockAfterRead(lock);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Removes all cache entries that have a <code>to</code> time point that is at or after <code>timePoint</code>.
158
+ */
159
+ public void invalidateAllAtOrLaterThan(TimePoint timePoint) {
160
+ LockUtil.lockForWrite(lock);
161
+ try {
162
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> dummy = createDummy(timePoint);
163
+ NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> toRemove = timeRangeCache.tailSet(dummy, /* inclusive */ true);
164
+ for (Iterator<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> i=toRemove.iterator(); i.hasNext(); ) {
165
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryToRemove = i.next();
166
+ assert entryToRemove.getA().compareTo(timePoint) >= 0;
167
+ for (Pair<TimePoint, T> fromAndResult : entryToRemove.getB()) {
168
+ lruCache.remove(new Util.Pair<>(fromAndResult.getA(), entryToRemove.getA()));
169
+ }
170
+ i.remove();
171
+ }
172
+ } finally {
173
+ LockUtil.unlockAfterWrite(lock);
174
+ }
175
+ }
176
+
177
+ private NavigableSet<Util.Pair<TimePoint, T>> getEntryForTo(TimePoint to) {
178
+ NavigableSet<Util.Pair<TimePoint, T>> result = null;
179
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> dummyForTo = createDummy(to);
180
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(dummyForTo);
181
+ if (entryForTo != null && entryForTo.getA().equals(to)) {
182
+ result = entryForTo.getB();
183
+ }
184
+ return result;
185
+ }
186
+
187
+ public void cache(TimePoint from, TimePoint to, T result) {
188
+ LockUtil.lockForWrite(lock);
189
+ try {
190
+ NavigableSet<Util.Pair<TimePoint, T>> entryForTo = getEntryForTo(to);
191
+ if (entryForTo == null) {
192
+ entryForTo = new ArrayListNavigableSet<Util.Pair<TimePoint, T>>(timePointInPairComparator);
193
+ Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> pairForTo = new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>(
194
+ to, entryForTo);
195
+ timeRangeCache.add(pairForTo);
196
+ }
197
+ entryForTo.add(new Util.Pair<TimePoint, T>(from, result));
198
+ lruCache.put(new Util.Pair<TimePoint, TimePoint>(from, to), null);
199
+ } finally {
200
+ LockUtil.unlockAfterWrite(lock);
201
+ }
202
+ }
203
+
204
+ private Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> createDummy(TimePoint to) {
205
+ return new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>(to, null);
206
+ }
207
+
208
+ /**
209
+ * Removes all contents from this cache
210
+ */
211
+ public void clear() {
212
+ LockUtil.lockForWrite(lock);
213
+ try {
214
+ timeRangeCache.clear();
215
+ lruCache.clear();
216
+ } finally {
217
+ LockUtil.unlockAfterWrite(lock);
218
+ }
219
+
220
+ }
221
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/TimedComparator.java
... ...
@@ -0,0 +1,17 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import java.io.Serializable;
4
+import java.util.Comparator;
5
+
6
+import com.sap.sse.common.Timed;
7
+
8
+public class TimedComparator implements Comparator<Timed>, Serializable {
9
+ private static final long serialVersionUID = 1604511471599854988L;
10
+ public static final Comparator<Timed> INSTANCE = new TimedComparator();
11
+
12
+ @Override
13
+ public int compare(Timed o1, Timed o2) {
14
+ return o1.getTimePoint().compareTo(o2.getTimePoint());
15
+ }
16
+}
17
+
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/TrackImpl.java
... ...
@@ -0,0 +1,549 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import java.io.IOException;
4
+import java.io.ObjectOutputStream;
5
+import java.util.ConcurrentModificationException;
6
+import java.util.Iterator;
7
+import java.util.NavigableSet;
8
+import java.util.function.Function;
9
+
10
+import com.sap.sailing.domain.shared.tracking.AddResult;
11
+import com.sap.sailing.domain.shared.tracking.FixAcceptancePredicate;
12
+import com.sap.sailing.domain.shared.tracking.Track;
13
+import com.sap.sse.common.Duration;
14
+import com.sap.sse.common.TimePoint;
15
+import com.sap.sse.common.Timed;
16
+import com.sap.sse.common.Util;
17
+import com.sap.sse.common.Util.Pair;
18
+import com.sap.sse.common.scalablevalue.ScalableValue;
19
+import com.sap.sse.concurrent.LockUtil;
20
+import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
21
+import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
22
+import com.sap.sse.shared.util.impl.UnmodifiableNavigableSet;
23
+
24
+public class TrackImpl<FixType extends Timed> implements Track<FixType> {
25
+ private static final long serialVersionUID = -4075853657857657528L;
26
+ /**
27
+ * The fixes, ordered by their time points
28
+ */
29
+ private final ArrayListNavigableSet<Timed> fixes;
30
+
31
+ private final NamedReentrantReadWriteLock readWriteLock;
32
+
33
+ protected static class DummyTimed implements Timed {
34
+ private static final long serialVersionUID = 6047311973718918856L;
35
+ private final TimePoint timePoint;
36
+ public DummyTimed(TimePoint timePoint) {
37
+ super();
38
+ this.timePoint = timePoint;
39
+ }
40
+ @Override
41
+ public TimePoint getTimePoint() {
42
+ return timePoint;
43
+ }
44
+ @Override
45
+ public String toString() {
46
+ return timePoint.toString();
47
+ }
48
+ }
49
+
50
+ public TrackImpl(String nameForReadWriteLock) {
51
+ this(new ArrayListNavigableSet<Timed>(TimedComparator.INSTANCE), nameForReadWriteLock);
52
+ }
53
+
54
+ protected TrackImpl(ArrayListNavigableSet<Timed> fixes, String nameForReadWriteLock) {
55
+ this.readWriteLock = new NamedReentrantReadWriteLock(nameForReadWriteLock, /* fair */ false);
56
+ this.fixes = fixes;
57
+ }
58
+
59
+ /**
60
+ * Synchronize the serialization such that no fixes are added while serializing
61
+ */
62
+ private void writeObject(ObjectOutputStream s) throws IOException {
63
+ lockForRead();
64
+ try {
65
+ s.defaultWriteObject();
66
+ } finally {
67
+ unlockAfterRead();
68
+ }
69
+ }
70
+
71
+ @Override
72
+ public void lockForRead() {
73
+ LockUtil.lockForRead(readWriteLock);
74
+ }
75
+
76
+ @Override
77
+ public void unlockAfterRead() {
78
+ LockUtil.unlockAfterRead(readWriteLock);
79
+ }
80
+
81
+ protected void lockForWrite() {
82
+ LockUtil.lockForWrite(readWriteLock);
83
+ }
84
+
85
+ protected void unlockAfterWrite() {
86
+ LockUtil.unlockAfterWrite(readWriteLock);
87
+ }
88
+
89
+ /**
90
+ * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and {@link #unlockAfterRead()}
91
+ * to avoid {@link ConcurrentModificationException}s. Should they modify the structure returned, they have to use
92
+ * {@link #lockForWrite()} and {@link #unlockAfterWrite()}, respectively.
93
+ */
94
+ protected NavigableSet<FixType> getInternalRawFixes() {
95
+ @SuppressWarnings("unchecked")
96
+ NavigableSet<FixType> result = (NavigableSet<FixType>) fixes;
97
+ return result;
98
+ }
99
+
100
+ /**
101
+ * asserts that the calling thread holds at least one of read and write lock
102
+ */
103
+ protected void assertReadLock() {
104
+ if (readWriteLock.getReadHoldCount() < 1 && readWriteLock.getWriteHoldCount() < 1) {
105
+ throw new IllegalStateException("Caller must obtain read lock using lockForRead() before calling this method");
106
+ }
107
+ }
108
+
109
+ protected void assertWriteLock() {
110
+ if (readWriteLock.getWriteHoldCount() < 1) {
111
+ throw new IllegalStateException("Caller must obtain write lock using lockForWrite() before calling this method");
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and
117
+ * {@link #unlockAfterRead()} to avoid {@link ConcurrentModificationException}s.
118
+ *
119
+ * @return the smoothened fixes ordered by their time points; this implementation simply delegates to
120
+ * {@link #getInternalRawFixes()} because for only {@link Timed} fixes we can't know how to remove outliers.
121
+ * Subclasses that constrain the <code>FixType</code> may provide smoothening implementations.
122
+ */
123
+ protected NavigableSet<FixType> getInternalFixes() {
124
+ NavigableSet<FixType> result = getInternalRawFixes();
125
+ return result;
126
+ }
127
+
128
+ /**
129
+ * Iterates the fixes with outliers getting skipped, in the order of their time points.
130
+ * Relies on {@link #getInternalFixes()} to void the track view from outliers.
131
+ */
132
+ @Override
133
+ public NavigableSet<FixType> getFixes() {
134
+ assertReadLock();
135
+ return new UnmodifiableNavigableSet<FixType>(getInternalFixes());
136
+ }
137
+
138
+ @Override
139
+ public Iterable<FixType> getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive) {
140
+ return getFixes().subSet(getDummyFix(from), fromInclusive, getDummyFix(to), toInclusive);
141
+ }
142
+
143
+ /**
144
+ * Iterates over the raw sequence of fixes, all potential outliers included
145
+ */
146
+ @Override
147
+ public NavigableSet<FixType> getRawFixes() {
148
+ assertReadLock();
149
+ return new UnmodifiableNavigableSet<FixType>(getInternalRawFixes());
150
+ }
151
+
152
+ @Override
153
+ public FixType getLastFixAtOrBefore(TimePoint timePoint) {
154
+ return getLastFixAtOrBefore(timePoint, /* fixAcceptancePredicate == null means accept all */ null);
155
+ }
156
+
157
+ private FixType getLastFixAtOrBefore(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
158
+ lockForRead();
159
+ try {
160
+ final NavigableSet<FixType> headSet = getInternalFixes().headSet(getDummyFix(timePoint), /* inclusive */ true);
161
+ for (final Iterator<FixType> i=headSet.descendingIterator(); i.hasNext(); ) {
162
+ final FixType next = i.next();
163
+ if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) {
164
+ return next;
165
+ }
166
+ }
167
+ return null;
168
+ } finally {
169
+ unlockAfterRead();
170
+ }
171
+ }
172
+
173
+ @Override
174
+ public FixType getLastFixBefore(TimePoint timePoint) {
175
+ lockForRead();
176
+ try {
177
+ return (FixType) getInternalFixes().lower(getDummyFix(timePoint));
178
+ } finally {
179
+ unlockAfterRead();
180
+ }
181
+ }
182
+
183
+ @Override
184
+ public FixType getLastRawFixAtOrBefore(TimePoint timePoint) {
185
+ lockForRead();
186
+ try {
187
+ return (FixType) getInternalRawFixes().floor(getDummyFix(timePoint));
188
+ } finally {
189
+ unlockAfterRead();
190
+ }
191
+ }
192
+
193
+ @Override
194
+ public FixType getFirstRawFixAtOrAfter(TimePoint timePoint) {
195
+ lockForRead();
196
+ try {
197
+ return (FixType) getInternalRawFixes().ceiling(getDummyFix(timePoint));
198
+ } finally {
199
+ unlockAfterRead();
200
+ }
201
+ }
202
+
203
+ @Override
204
+ public FixType getFirstFixAtOrAfter(TimePoint timePoint) {
205
+ return getFirstFixAtOrAfter(timePoint, /* fixAcceptancePredicate==null means accept all fixes */ null);
206
+ }
207
+
208
+ private FixType getFirstFixAtOrAfter(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
209
+ lockForRead();
210
+ try {
211
+ final NavigableSet<FixType> tailSet = getInternalFixes().tailSet(getDummyFix(timePoint), /* inclusive */ true);
212
+ for (final FixType next : tailSet) {
213
+ if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) {
214
+ return next;
215
+ }
216
+ }
217
+ return null;
218
+ } finally {
219
+ unlockAfterRead();
220
+ }
221
+ }
222
+
223
+ @Override
224
+ public FixType getLastRawFixBefore(TimePoint timePoint) {
225
+ lockForRead();
226
+ try {
227
+ return (FixType) getInternalRawFixes().lower(getDummyFix(timePoint));
228
+ } finally {
229
+ unlockAfterRead();
230
+ }
231
+ }
232
+
233
+ @Override
234
+ public FixType getFirstFixAfter(TimePoint timePoint) {
235
+ lockForRead();
236
+ try {
237
+ return (FixType) getInternalFixes().higher(getDummyFix(timePoint));
238
+ } finally {
239
+ unlockAfterRead();
240
+ }
241
+ }
242
+
243
+ @Override
244
+ public FixType getFirstRawFixAfter(TimePoint timePoint) {
245
+ lockForRead();
246
+ try {
247
+ return (FixType) getInternalRawFixes().higher(getDummyFix(timePoint));
248
+ } finally {
249
+ unlockAfterRead();
250
+ }
251
+ }
252
+
253
+ @Override
254
+ public FixType getFirstRawFix() {
255
+ lockForRead();
256
+ try {
257
+ if (getInternalFixes().isEmpty()) {
258
+ return null;
259
+ } else {
260
+ return (FixType) getInternalFixes().first();
261
+ }
262
+ } finally {
263
+ unlockAfterRead();
264
+ }
265
+ }
266
+
267
+ @Override
268
+ public FixType getLastRawFix() {
269
+ lockForRead();
270
+ try {
271
+ if (getInternalRawFixes().isEmpty()) {
272
+ return null;
273
+ } else {
274
+ return (FixType) getInternalRawFixes().last();
275
+ }
276
+ } finally {
277
+ unlockAfterRead();
278
+ }
279
+ }
280
+
281
+ /**
282
+ * @param fixAcceptancePredicate
283
+ * if not {@code null}, adjacent fixes will be skipped as long as this predicate does not
284
+ * {@link FixAcceptancePredicate#isAcceptFix(Object) accept} the fix. This can, e.g., be used to skip
285
+ * fixes that don't have values in a dimension required. If {@code null}, the next fixes left and right
286
+ * (including the exact {@code timePoint} if a fix exists there) will be used without further check.
287
+ */
288
+ private Pair<FixType, FixType> getSurroundingFixes(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
289
+ FixType left = getLastFixAtOrBefore(timePoint, fixAcceptancePredicate);
290
+ FixType right = getFirstFixAtOrAfter(timePoint, fixAcceptancePredicate);
291
+ com.sap.sse.common.Util.Pair<FixType, FixType> result = new com.sap.sse.common.Util.Pair<>(left, right);
292
+ return result;
293
+ }
294
+
295
+ /**
296
+ * Calculates a linear interpolation of values based on their time points and a target time point that is expected
297
+ * to be in between (inclusive) the two time points for the two values. If the two time points for the two values
298
+ * are equal, the average of the two values is returned.
299
+ */
300
+ private <V, T> T timeBasedAverage(TimePoint timePoint, ScalableValue<V, T> value1, TimePoint timePoint1, ScalableValue<V, T> value2, TimePoint timePoint2) {
301
+ final T acc;
302
+ if (timePoint1.equals(timePoint2)) {
303
+ acc = value1.add(value2).divide(2);
304
+ } else {
305
+ long timeDiff1 = Math.abs(timePoint1.asMillis() - timePoint.asMillis());
306
+ long timeDiff2 = Math.abs(timePoint2.asMillis() - timePoint.asMillis());
307
+ acc = value1.multiply(timeDiff2).add(value2.multiply(timeDiff1)).divide(timeDiff1 + timeDiff2);
308
+ }
309
+ return acc;
310
+ }
311
+
312
+ @Override
313
+ public <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
314
+ Function<FixType, ScalableValue<InternalType, ValueType>> converter) {
315
+ return getInterpolatedValue(timePoint, converter, /* fixAcceptancePredicate==null means accept all */ null);
316
+ }
317
+
318
+ protected <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
319
+ Function<FixType, ScalableValue<InternalType, ValueType>> converter, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
320
+ final ValueType result;
321
+ Pair<FixType, FixType> fixPair = getSurroundingFixes(timePoint, fixAcceptancePredicate);
322
+ if (fixPair.getA() == null) {
323
+ if (fixPair.getB() == null) {
324
+ result = null;
325
+ } else {
326
+ result = converter.apply(fixPair.getB()).divide(1);
327
+ }
328
+ } else {
329
+ if (fixPair.getB() == null || fixPair.getA() == fixPair.getB()) {
330
+ result = converter.apply(fixPair.getA()).divide(1);
331
+ } else {
332
+ result = timeBasedAverage(timePoint,
333
+ converter.apply(fixPair.getA()), fixPair.getA().getTimePoint(),
334
+ converter.apply(fixPair.getB()), fixPair.getB().getTimePoint());
335
+ }
336
+ }
337
+ return result;
338
+ }
339
+
340
+ @Override
341
+ public Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive) {
342
+ assertReadLock();
343
+ return getTimeConstrainedFixesIterator(getInternalFixes(), startingAt, inclusive, /* endingAt */ null, /* endingAtInclusive */ false);
344
+ }
345
+
346
+ @Override
347
+ public Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt,
348
+ boolean endingAtInclusive) {
349
+ assertReadLock();
350
+ return getTimeConstrainedFixesIterator(getInternalFixes(), startingAt, startingAtInclusive, endingAt, endingAtInclusive);
351
+ }
352
+
353
+ @Override
354
+ public Iterator<FixType> getFixesDescendingIterator(TimePoint startingAt, boolean inclusive) {
355
+ assertReadLock();
356
+ Iterator<FixType> result = (Iterator<FixType>) getInternalFixes().headSet(
357
+ getDummyFix(startingAt), inclusive).descendingIterator();
358
+ return result;
359
+ }
360
+
361
+ /**
362
+ * Creates a dummy fix that conforms to <code>FixType</code>. This in particular means that subclasses
363
+ * instantiating <code>FixType</code> with a specific class need to redefine this method so as to return
364
+ * a dummy fix complying with their instantiation type used for <code>FixType</code>. Otherwise, a
365
+ * {@link ClassCastException} may result upon certain operations performed with the fix returned by
366
+ * this method.
367
+ */
368
+ protected FixType getDummyFix(TimePoint timePoint) {
369
+ @SuppressWarnings("unchecked")
370
+ FixType result = (FixType) new DummyTimed(timePoint);
371
+ return result;
372
+ }
373
+
374
+ @Override
375
+ public Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean inclusive) {
376
+ assertReadLock();
377
+ return getTimeConstrainedFixesIterator(getInternalRawFixes(), startingAt, inclusive, /* endingAt */ null, /* endingAtInclusive */ false);
378
+ }
379
+
380
+ private Iterator<FixType> getTimeConstrainedFixesIterator(NavigableSet<FixType> set, TimePoint startingAt, boolean startingAtInclusive,
381
+ TimePoint endingAt, boolean endingAtInclusive) {
382
+ assertReadLock();
383
+ if (startingAt != null && endingAt != null) {
384
+ set = set.subSet(getDummyFix(startingAt), startingAtInclusive, getDummyFix(endingAt), endingAtInclusive);
385
+ } else if (endingAt != null) {
386
+ set = set.headSet(getDummyFix(endingAt), endingAtInclusive);
387
+ } else if (startingAt != null) {
388
+ set = set.tailSet(getDummyFix(startingAt), startingAtInclusive);
389
+ }
390
+ Iterator<FixType> result = set.iterator();
391
+ return result;
392
+ }
393
+
394
+ @Override
395
+ public Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean startingAtInclusive,
396
+ TimePoint endingAt, boolean endingAtInclusive) {
397
+ assertReadLock();
398
+ return getTimeConstrainedFixesIterator(getInternalRawFixes(), startingAt, startingAtInclusive, endingAt, endingAtInclusive);
399
+ }
400
+
401
+ @Override
402
+ public Iterator<FixType> getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive) {
403
+ assertReadLock();
404
+ Iterator<FixType> result = (Iterator<FixType>) getInternalRawFixes().headSet(
405
+ getDummyFix(startingAt), inclusive).descendingIterator();
406
+ return result;
407
+ }
408
+
409
+ protected boolean add(FixType fix) {
410
+ return add(fix, /* replace */ false);
411
+ }
412
+
413
+ /**
414
+ * @return {@code true} if the fix was added or replaced; {@code false} in case no change was performed
415
+ */
416
+ protected boolean add(FixType fix, boolean replace) {
417
+ lockForWrite();
418
+ try {
419
+ final AddResult addResult = addWithoutLocking(fix, replace);
420
+ return addResult == AddResult.ADDED || addResult == AddResult.REPLACED;
421
+ } finally {
422
+ unlockAfterWrite();
423
+ }
424
+ }
425
+
426
+ /**
427
+ * The caller must ensure to hold the write lock for this track when calling this method.
428
+ *
429
+ * @param replace
430
+ * whether or not to replace an existing fix in the track that is equal to {@link #fix} as defined by the
431
+ * comparator used for the {@link #fixes} set. By default this is a comparator only comparing the
432
+ * fixes' time stamps. Subclasses may use different comparator implementations.
433
+ */
434
+ protected AddResult addWithoutLocking(FixType fix, boolean replace) {
435
+ final AddResult result;
436
+ final boolean added = getInternalRawFixes().add(fix);
437
+ if (!added && replace) {
438
+ getInternalRawFixes().remove(fix);
439
+ result = getInternalRawFixes().add(fix) ? AddResult.REPLACED : AddResult.NOT_ADDED;
440
+ } else {
441
+ result = added ? AddResult.ADDED : AddResult.NOT_ADDED;
442
+ }
443
+ return result;
444
+ }
445
+
446
+ @Override
447
+ public Duration getAverageIntervalBetweenFixes() {
448
+ lockForRead();
449
+ try {
450
+ final Duration result;
451
+ final int size = getRawFixes().size();
452
+ if (size > 1) {
453
+ result = getRawFixes().first().getTimePoint().until(getRawFixes().last().getTimePoint()).divide(size-1);
454
+ } else {
455
+ result = null;
456
+ }
457
+ return result;
458
+ } finally {
459
+ unlockAfterRead();
460
+ }
461
+ }
462
+
463
+ @Override
464
+ public Duration getAverageIntervalBetweenRawFixes() {
465
+ lockForRead();
466
+ try {
467
+ final Duration result;
468
+ final int size = getRawFixes().size();
469
+ if (size > 1) {
470
+ result = getRawFixes().first().getTimePoint().until(getRawFixes().last().getTimePoint()).divide(size-1);
471
+ } else {
472
+ result = null;
473
+ }
474
+ return result;
475
+ } finally {
476
+ unlockAfterRead();
477
+ }
478
+ }
479
+
480
+ @Override
481
+ public <T> T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator) {
482
+ return getValueSumRecursively(from, to, /* recursionLevel */ 0, nullElement, adder, cache, valueCalculator);
483
+ }
484
+
485
+ private <T> T getValueSumRecursively(TimePoint from, TimePoint to, int recursionDepth, T nullElement,
486
+ Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator) {
487
+ T result;
488
+ if (!from.before(to)) {
489
+ result = nullElement;
490
+ } else {
491
+ boolean perfectCacheHit = false;
492
+ lockForRead();
493
+ try {
494
+ Util.Pair<TimePoint, Util.Pair<TimePoint, T>> bestCacheEntry = cache.getEarliestFromAndResultAtOrAfterFrom(from, to);
495
+ if (bestCacheEntry != null) {
496
+ perfectCacheHit = true; // potentially a cache hit; but if it doesn't span the full interval, it's not perfect; see below
497
+ // compute the missing stretches between best cache entry's "from" and our "from" and the cache
498
+ // entry's "to" and our "to"
499
+ T valueFromFromToBeginningOfCacheEntry = nullElement;
500
+ T valueFromEndOfCacheEntryToTo = nullElement;
501
+ if (!bestCacheEntry.getB().getA().equals(from)) {
502
+ assert bestCacheEntry.getB().getA().after(from);
503
+ perfectCacheHit = false;
504
+ valueFromFromToBeginningOfCacheEntry = getValueSumRecursively(from, bestCacheEntry
505
+ .getB().getA(), recursionDepth + 1, nullElement, adder, cache, valueCalculator);
506
+ }
507
+ if (!bestCacheEntry.getA().equals(to)) {
508
+ assert bestCacheEntry.getA().before(to);
509
+ perfectCacheHit = false;
510
+ valueFromEndOfCacheEntryToTo = getValueSumRecursively(bestCacheEntry.getA(), to,
511
+ recursionDepth + 1, nullElement, adder, cache, valueCalculator);
512
+ }
513
+ if (valueFromEndOfCacheEntryToTo == null || bestCacheEntry.getB().getB() == null) {
514
+ result = null;
515
+ } else {
516
+ result = adder.add(adder.add(valueFromFromToBeginningOfCacheEntry, bestCacheEntry.getB().getB()),
517
+ valueFromEndOfCacheEntryToTo);
518
+ }
519
+ } else {
520
+ if (from.compareTo(to) < 0) {
521
+ result = valueCalculator.calculate(from, to);
522
+ } else {
523
+ result = nullElement;
524
+ }
525
+ }
526
+ // run the cache update while still holding the read lock; this avoids bug4629 where a cache invalidation
527
+ // caused by fix insertions can come after the result calculation and before the cache update
528
+ if (!perfectCacheHit && recursionDepth == 0) {
529
+ cache.cache(from, to, result);
530
+ }
531
+ } finally {
532
+ unlockAfterRead();
533
+ }
534
+ }
535
+ return result;
536
+ }
537
+
538
+
539
+
540
+ @Override
541
+ public int size() {
542
+ return fixes.size();
543
+ }
544
+
545
+ @Override
546
+ public boolean isEmpty() {
547
+ return fixes.isEmpty();
548
+ }
549
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/shared/tracking/impl/TrackingConnectorInfoImpl.java
... ...
@@ -0,0 +1,66 @@
1
+package com.sap.sailing.domain.shared.tracking.impl;
2
+
3
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
4
+
5
+public class TrackingConnectorInfoImpl implements TrackingConnectorInfo {
6
+ private static final long serialVersionUID = 7970268841592389145L;
7
+ private final String trackingConnectorName;
8
+ private final String TtackingConnectorDefaultUrl;
9
+ private final String webUrl;
10
+
11
+ public TrackingConnectorInfoImpl(String trackingConnectorName, String trackingConnectorDefaultUrl, String webUrl) {
12
+ super();
13
+ this.trackingConnectorName = trackingConnectorName;
14
+ TtackingConnectorDefaultUrl = trackingConnectorDefaultUrl;
15
+ this.webUrl = webUrl;
16
+ }
17
+
18
+ public String getTrackingConnectorDefaultUrl() {
19
+ return TtackingConnectorDefaultUrl;
20
+ }
21
+
22
+ public String getTrackingConnectorName() {
23
+ return trackingConnectorName;
24
+ }
25
+
26
+ public String getWebUrl() {
27
+ return webUrl;
28
+ }
29
+
30
+ @Override
31
+ public int hashCode() {
32
+ final int prime = 31;
33
+ int result = 1;
34
+ result = prime * result + ((TtackingConnectorDefaultUrl == null) ? 0 : TtackingConnectorDefaultUrl.hashCode());
35
+ result = prime * result + ((trackingConnectorName == null) ? 0 : trackingConnectorName.hashCode());
36
+ result = prime * result + ((webUrl == null) ? 0 : webUrl.hashCode());
37
+ return result;
38
+ }
39
+
40
+ @Override
41
+ public boolean equals(Object obj) {
42
+ if (this == obj)
43
+ return true;
44
+ if (obj == null)
45
+ return false;
46
+ if (getClass() != obj.getClass())
47
+ return false;
48
+ TrackingConnectorInfoImpl other = (TrackingConnectorInfoImpl) obj;
49
+ if (TtackingConnectorDefaultUrl == null) {
50
+ if (other.TtackingConnectorDefaultUrl != null)
51
+ return false;
52
+ } else if (!TtackingConnectorDefaultUrl.equals(other.TtackingConnectorDefaultUrl))
53
+ return false;
54
+ if (trackingConnectorName == null) {
55
+ if (other.trackingConnectorName != null)
56
+ return false;
57
+ } else if (!trackingConnectorName.equals(other.trackingConnectorName))
58
+ return false;
59
+ if (webUrl == null) {
60
+ if (other.webUrl != null)
61
+ return false;
62
+ } else if (!webUrl.equals(other.webUrl))
63
+ return false;
64
+ return true;
65
+ }
66
+}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/AddResult.java
... ...
@@ -1,14 +0,0 @@
1
-package com.sap.sailing.domain.tracking;
2
-
3
-import javax.swing.plaf.basic.BasicSliderUI.TrackListener;
4
-
5
-/**
6
- * The result of trying to add a fix to a {@link Track}. Used when notifying {@link TrackListener}s.
7
- * This allows listeners, in particular, to distinguish between the add and replace scenario.
8
- *
9
- * @author Axel Uhl (D043530)
10
- *
11
- */
12
-public enum AddResult {
13
- NOT_ADDED, ADDED, REPLACED;
14
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/FixAcceptancePredicate.java
... ...
@@ -1,14 +0,0 @@
1
-package com.sap.sailing.domain.tracking;
2
-
3
-/**
4
- * A predicate for a fix, for use in
5
- * {@link Track#getInterpolatedValue(com.sap.sse.common.TimePoint, com.sap.sse.common.Util.Function)}, used, e.g., to
6
- * provide a rule for when a fix shall be accepted during the search for surrounding fixes.
7
- *
8
- * @author Axel Uhl (d043530)
9
- *
10
- * @param <FixType>
11
- */
12
-public interface FixAcceptancePredicate<FixType> {
13
- boolean isAcceptFix(FixType fix);
14
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/LineDetails.java
... ...
@@ -1,57 +0,0 @@
1
-package com.sap.sailing.domain.tracking;
2
-
3
-import com.sap.sailing.domain.base.Mark;
4
-import com.sap.sailing.domain.base.Waypoint;
5
-import com.sap.sailing.domain.common.NauticalSide;
6
-import com.sap.sailing.domain.common.Position;
7
-import com.sap.sse.common.Bearing;
8
-import com.sap.sse.common.Distance;
9
-import com.sap.sse.common.TimePoint;
10
-
11
-/**
12
- * For a line such as a start or a finish line, tells the line's length at a given time, which side is
13
- * {@link NauticalSide#PORT port} and which is {@link NauticalSide#STARBOARD starboard} when approaching the line,
14
- * and---if wind information is available---its angle to a true wind direction and the advantageous side in approaching
15
- * direction as well as how much the advantageous side is ahead. The wind-dependent information
16
- * will all be <code>null</code> if no wind data is available.
17
- *
18
- * @author Axel Uhl (d043530)
19
- *
20
- */
21
-public interface LineDetails {
22
- TimePoint getTimePoint();
23
-
24
- Waypoint getWaypoint();
25
-
26
- Distance getLength();
27
-
28
- Bearing getAngleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind();
29
-
30
- NauticalSide getAdvantageousSideWhileApproachingLine();
31
-
32
- Mark getStarboardMarkWhileApproachingLine();
33
-
34
- Mark getPortMarkWhileApproachingLine();
35
-
36
- Distance getAdvantage();
37
-
38
- Position getPortMarkPosition();
39
-
40
- Position getStarboardMarkPosition();
41
-
42
- default Mark getAdvantageousMark() {
43
- return getAdvantageousSideWhileApproachingLine() == NauticalSide.PORT ? getPortMarkWhileApproachingLine() : getStarboardMarkWhileApproachingLine();
44
- }
45
-
46
- default Position getAdvantageousMarkPosition() {
47
- return getAdvantageousSideWhileApproachingLine() == NauticalSide.PORT ? getPortMarkPosition() : getStarboardMarkPosition();
48
- }
49
-
50
- default Bearing getBearingFromStarboardToPortWhenApproachingLine() {
51
- return getStarboardMarkPosition().getBearingGreatCircle(getPortMarkPosition());
52
- }
53
-
54
- default Bearing getBearingFromPortToStarboardWhenApproachingLine() {
55
- return getPortMarkPosition().getBearingGreatCircle(getStarboardMarkPosition());
56
- }
57
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/MappedTrack.java
... ...
@@ -1,20 +0,0 @@
1
-package com.sap.sailing.domain.tracking;
2
-
3
-import com.sap.sse.common.Timed;
4
-
5
-/**
6
- * {@link Track} specialization, which is mapped to a specific type of items.
7
- *
8
- * @param <ItemType>
9
- * the type of item this track is mapped to
10
- * @param <FixType>
11
- * the type of fix that is contained in this track
12
- */
13
-public interface MappedTrack<ItemType, FixType extends Timed> extends Track<FixType> {
14
-
15
- /**
16
- * @return the item this track is mapped to.
17
- */
18
- ItemType getTrackedItem();
19
-
20
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java
... ...
@@ -1,259 +0,0 @@
1
-package com.sap.sailing.domain.tracking;
2
-
3
-import java.io.Serializable;
4
-import java.util.Iterator;
5
-import java.util.concurrent.locks.ReadWriteLock;
6
-import java.util.function.Function;
7
-
8
-import com.sap.sailing.domain.common.tracking.GPSFixMoving;
9
-import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
10
-import com.sap.sse.common.Duration;
11
-import com.sap.sse.common.TimePoint;
12
-import com.sap.sse.common.Timed;
13
-import com.sap.sse.common.scalablevalue.ScalableValue;
14
-
15
-/**
16
- * A track records {@link Timed} items for an object of type <code>ItemType</code>. It allows clients to ask for a value
17
- * close to a given {@link TimePoint}. The track manages a time-based set of raw fixes. An implementation may have an
18
- * understanding of how to eliminate outliers. For example, if a track implementation knows it's tracking boats, it may
19
- * consider fixes that the boat cannot possibly have reached due to its speed and direction change limitations as
20
- * outliers. The set of fixes with outliers filtered out can be obtained using {@link #getFixes} whereas
21
- * {@link #getRawFixes()} returns the unfiltered, raw fixes. If an implementation has no idea what an outlier is,
22
- * both methods will return the same fix sequence.<p>
23
- *
24
- * With tracks, concurrency is an important issue. Threads may want to modify a track while other threads may want to
25
- * read from it. Several methods such as {@link #getLastFixAtOrBefore(TimePoint)} return a single fix and can manage
26
- * concurrency internally. However, those methods returning a collection of fixes, such as {@link #getFixes()} or an
27
- * iterator over a collection of fixes, such as {@link #getFixesIterator(TimePoint, boolean)}, need special treatment.
28
- * Until we internalize such iterations (see bug 824, http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=824),
29
- * callers need to manage a read lock which is part of a {@link ReadWriteLock} managed by this track. Callers do so
30
- * by calling {@link #lockForRead} and {@link #unlockAfterRead}.
31
- *
32
- * @author Axel Uhl (d043530)
33
- */
34
-public interface Track<FixType extends Timed> extends Serializable {
35
- /**
36
- * An adding function to be used together with {@link Track#getValueSum(TimePoint, TimePoint, Object, Adder, TimeRangeCache, TimeRangeValueCalculator)}.
37
- *
38
- * @author Axel Uhl (D043530)
39
- *
40
- * @param <T>
41
- */
42
- static interface Adder<T> {
43
- /**
44
- * Adds two elements of type {@code T}. Neither argument must be {@code null}.
45
- */
46
- T add(T t1, T t2);
47
- }
48
-
49
- static interface TimeRangeValueCalculator<T> {
50
- /**
51
- * Calculates a value for fixes across a time range. When the method is called,
52
- * a read lock will previously have been {@link Track#lockForRead obtained} before,
53
- * so an implementing class does not need to worry about acquiring the lock.
54
- */
55
- T calculate(TimePoint from, TimePoint to);
56
- }
57
-
58
- /**
59
- * Locks this track for reading by the calling thread. If the thread already holds the lock for this track,
60
- * the hold count will be incremented. Make sure to call {@link #unlockAfterRead()} in a <code>finally</code>
61
- * block to release the lock under all possible circumstances. Failure to do so will inevitably lead to
62
- * deadlocks!
63
- */
64
- void lockForRead();
65
-
66
- /**
67
- * Decrements the hold count for this track's read lock for the calling thread. If it goes to zero, the lock will be
68
- * released and other readers or a writer can obtain the lock. Make sure to call this method in a
69
- * <code>finally</code> block for each {@link #lockForRead()} invocation.
70
- */
71
- void unlockAfterRead();
72
-
73
- /**
74
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
75
- * will be thrown in case the caller has failed to do so.
76
- *
77
- * @return the smoothened fixes
78
- */
79
- Iterable<FixType> getFixes();
80
-
81
- /**
82
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
83
- * will be thrown in case the caller has failed to do so.
84
- *
85
- * @return The smoothened fixes between from and to.
86
- */
87
- Iterable<FixType> getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive);
88
-
89
- /**
90
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
91
- * will be thrown in case the caller has failed to do so.
92
- */
93
- Iterable<FixType> getRawFixes();
94
-
95
- /**
96
- * Returns <code>null</code> if no such fix exists.
97
- */
98
- FixType getLastFixAtOrBefore(TimePoint timePoint);
99
-
100
- /**
101
- * Returns <code>null</code> if no such fix exists.
102
- */
103
- FixType getLastFixBefore(TimePoint timePoint);
104
-
105
- /**
106
- * Returns <code>null</code> if no such fix exists.
107
- */
108
- FixType getLastRawFixAtOrBefore(TimePoint timePoint);
109
-
110
- /**
111
- * Returns <code>null</code> if no such fix exists.
112
- */
113
- FixType getFirstFixAtOrAfter(TimePoint timePoint);
114
-
115
- /**
116
- * Returns <code>null</code> if no such fix exists.
117
- */
118
- FixType getFirstRawFixAtOrAfter(TimePoint timePoint);
119
-
120
- /**
121
- * Returns <code>null</code> if no such fix exists.
122
- */
123
- FixType getLastRawFixBefore(TimePoint timePoint);
124
-
125
- /**
126
- * Returns <code>null</code> if no such fix exists.
127
- */
128
- FixType getFirstRawFixAfter(TimePoint timePoint);
129
-
130
- /**
131
- * Returns <code>null</code> if no such fix exists.
132
- */
133
- FixType getFirstFixAfter(TimePoint timePoint);
134
-
135
- /**
136
- * The first fix in this track or <code>null</code> if the track is empty. The fix returned may
137
- * be an outlier that is not returned by calls operating on the smoothened version of the track.
138
- */
139
- FixType getFirstRawFix();
140
-
141
- /**
142
- * The last fix in this track or <code>null</code> if the track is empty. The fix returned may
143
- * be an outlier that is not returned by calls operating on the smoothened version of the track.
144
- */
145
- FixType getLastRawFix();
146
-
147
- /**
148
- * Interpolates an aspect of the fixes in this track for a given {@code timePoint}. If {@code timePoint} matches
149
- * exactly a fix in this track, that fix is used. If this track is empty, {@code null} is returned. If the
150
- * {@code timePoint} is after the last fix of this track, the last fix is used; if before the first fix, the first
151
- * fix is used.
152
- * <p>
153
- *
154
- * The fix(es) are converted to {@link ScalableValue}s using the {@code converter} which gives callers a choice
155
- * which aspect of the fixes to project and interpolate. If more than one value results because two fixes (one
156
- * before, one after) are used, linear interpolation based on the fixes' time points takes place.
157
- * <p>
158
- *
159
- * Example: for a track of {@link GPSFixMoving} fixes the course over ground shall be determined for a given time
160
- * point. The call would look like this:
161
- * {@code getInterpolatedValue(timePoint, f->new ScalableBearing(f.getSpeed().getBearing()))}
162
- *
163
- * @return the projected interpolated value, typed by what the {@link ScalableValue#divide(double)} method returns.
164
- */
165
- <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
166
- Function<FixType, ScalableValue<InternalType, ValueType>> converter);
167
-
168
- /**
169
- * Returns an iterator starting at the first fix after <code>startingAt</code> (or "at or after" in case
170
- * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator exclude outliers (see
171
- * also {@link #getFixes()} and returns the remaining fixes without any smoothening or dampening applied.
172
- *
173
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
174
- * will be thrown in case the caller has failed to do so.
175
- */
176
- Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive);
177
-
178
- /**
179
- * Returns an iterator starting at the first fix after <code>startingAt</code> (or "at or after" in case
180
- * <code>inclusive</code> is <code>true</code>) and that ends at the <code>endingAt</code> time point or just before
181
- * in case <code>endingAtIncluive</code> is false. The fixes returned by the iterator are the smoothened fixes (see
182
- * also {@link #getFixes()}, without any smoothening or dampening applied.
183
- *
184
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an
185
- * exception will be thrown in case the caller has failed to do so.
186
- *
187
- * @param startingAt
188
- * if <code>null</code>, starts with the first fix available
189
- * @param endingAt
190
- * if <code>null</code>., ends with the last fix available
191
- */
192
- Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt, boolean endingAtInclusive);
193
-
194
- /**
195
- * Returns an iterator starting at the first raw fix after <code>startingAt</code> (or "at or after" in case
196
- * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the raw fixes (see also
197
- * {@link #getRawFixes()}, without any smoothening or dampening applied.
198
- *
199
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
200
- * will be thrown in case the caller has failed to do so.
201
- */
202
- Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean inclusive);
203
-
204
- /**
205
- * Returns an iterator starting at the first raw fix after <code>startingAt</code> (or "at or after" in case
206
- * <code>startingAtInclusive</code> is <code>true</code>) and ending at the <code>endingAt</code> time point or just before
207
- * in case <code>endingAtIncluive</code> is false. The fixes returned by the iterator are the raw fixes (see also
208
- * {@link #getRawFixes()}, without any smoothening or dampening applied.
209
- *
210
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
211
- * will be thrown in case the caller has failed to do so.
212
- */
213
- Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt, boolean endingAtInclusive);
214
-
215
- /**
216
- * Returns a descending iterator starting at the first fix before <code>startingAt</code> (or "at or before" in case
217
- * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the smoothened fixes (see
218
- * also {@link #getFixes()}, without any smoothening or dampening applied.
219
- *
220
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
221
- * will be thrown in case the caller has failed to do so.
222
- */
223
- Iterator<FixType> getFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
224
-
225
- /**
226
- * Returns a descending iterator starting at the first raw fix before <code>startingAt</code> (or "at or before" in case
227
- * <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the raw fixes (see also
228
- * {@link #getRawFixes()}, without any smoothening or dampening applied.
229
- *
230
- * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
231
- * will be thrown in case the caller has failed to do so.
232
- */
233
- Iterator<FixType> getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
234
-
235
- /**
236
- * @return the average duration between two fixes (outliers removed) in this track or <code>null</code> if there is not
237
- * more than one fix in the track
238
- */
239
- Duration getAverageIntervalBetweenFixes();
240
-
241
- /**
242
- * @return the average duration between two fixes (outliers <em>not</em> removed) in this track or <code>null</code> if there is not
243
- * more than one raw fix in the track
244
- */
245
- Duration getAverageIntervalBetweenRawFixes();
246
-
247
- <T> T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator);
248
-
249
- /**
250
- * @return the number of raw fixes contained in the Track.
251
- */
252
- int size();
253
-
254
- /**
255
- * Tells whether the collection of {@link #getRawFixes() raw fixes} (no outliers removed) is empty
256
- */
257
- boolean isEmpty();
258
-
259
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/TrackingConnectorInfo.java
... ...
@@ -1,28 +0,0 @@
1
-
2
-package com.sap.sailing.domain.tracking;
3
-
4
-import java.io.Serializable;
5
-
6
-/**
7
- * Identifies the tracking connector that was used to create a TrackedRace.
8
- * Further the Connector can provide a webUrl, that leads to an event web page.
9
- */
10
-public interface TrackingConnectorInfo extends Serializable {
11
-
12
- /**
13
- * gets the name associated with the tracking technology used for the Race
14
- */
15
- String getTrackingConnectorName();
16
-
17
- /**
18
- * gets a {@link String} representation of the default web-URL associated with the tracking technology used for the Race.
19
- * may be {@code null} if there is none provided in the adapter.
20
- */
21
- String getTrackingConnectorDefaultUrl();
22
-
23
- /**
24
- * gets a {@link String} representation of the web-URL associated with the Event.
25
- * may be {@code null} if the API of the respective Tracking-Service does not provide a URL.
26
- */
27
- String getWebUrl();
28
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/LineDetailsImpl.java
... ...
@@ -1,91 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import com.sap.sailing.domain.base.Mark;
4
-import com.sap.sailing.domain.base.Waypoint;
5
-import com.sap.sailing.domain.common.NauticalSide;
6
-import com.sap.sailing.domain.common.Position;
7
-import com.sap.sailing.domain.tracking.LineDetails;
8
-import com.sap.sse.common.Bearing;
9
-import com.sap.sse.common.Distance;
10
-import com.sap.sse.common.TimePoint;
11
-
12
-public class LineDetailsImpl implements LineDetails {
13
- private final TimePoint timePoint;
14
- private final Waypoint waypoint;
15
- private final Distance length;
16
- private final Bearing angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
17
- private final NauticalSide advantageousSidewhileApproachingLine;
18
- private final Distance advantage;
19
- private final Mark portMarkWhileApproachingLine;
20
- private final Mark starboardMarkWhileApproachingLine;
21
- private final Position portMarkPosition;
22
- private final Position starboardMarkPosition;
23
-
24
- public LineDetailsImpl(TimePoint timePoint, Waypoint waypoint, Distance length,
25
- Bearing angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind, NauticalSide advantageousSideWhileApproachingLine,
26
- Distance advantage, Mark portMarkWhileApproachingLine, Mark starboardMarkWhileApproachingLine,
27
- Position portMarkPosition, Position starboardMarkPosition) {
28
- super();
29
- this.timePoint = timePoint;
30
- this.waypoint = waypoint;
31
- this.length = length;
32
- this.angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind = angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
33
- this.advantageousSidewhileApproachingLine = advantageousSideWhileApproachingLine;
34
- this.advantage = advantage;
35
- this.portMarkWhileApproachingLine = portMarkWhileApproachingLine;
36
- this.starboardMarkWhileApproachingLine = starboardMarkWhileApproachingLine;
37
- this.portMarkPosition = portMarkPosition;
38
- this.starboardMarkPosition = starboardMarkPosition;
39
- }
40
-
41
- @Override
42
- public TimePoint getTimePoint() {
43
- return timePoint;
44
- }
45
-
46
- @Override
47
- public Waypoint getWaypoint() {
48
- return waypoint;
49
- }
50
-
51
- @Override
52
- public Distance getLength() {
53
- return length;
54
- }
55
-
56
- @Override
57
- public Bearing getAngleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind() {
58
- return angleDifferenceFromPortToStarboardWhenApproachingLineToTrueWind;
59
- }
60
-
61
- @Override
62
- public NauticalSide getAdvantageousSideWhileApproachingLine() {
63
- return advantageousSidewhileApproachingLine;
64
- }
65
-
66
- @Override
67
- public Distance getAdvantage() {
68
- return advantage;
69
- }
70
-
71
- @Override
72
- public Mark getStarboardMarkWhileApproachingLine() {
73
- return starboardMarkWhileApproachingLine;
74
- }
75
-
76
- @Override
77
- public Mark getPortMarkWhileApproachingLine() {
78
- return portMarkWhileApproachingLine;
79
- }
80
-
81
- @Override
82
- public Position getPortMarkPosition() {
83
- return portMarkPosition;
84
- }
85
-
86
- @Override
87
- public Position getStarboardMarkPosition() {
88
- return starboardMarkPosition;
89
- }
90
-
91
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/MappedTrackImpl.java
... ...
@@ -1,39 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import com.sap.sailing.domain.tracking.MappedTrack;
4
-import com.sap.sse.common.Timed;
5
-import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
6
-
7
-/**
8
- * Default implementation of {@link MappedTrack} interface.
9
- *
10
- * @param <ItemType>
11
- * the type of item this track is mapped to
12
- * @param <FixType>
13
- * the type of fix that is contained in this track
14
- */
15
-public class MappedTrackImpl<ItemType, FixType extends Timed> extends TrackImpl<FixType>
16
- implements MappedTrack<ItemType, FixType> {
17
-
18
- private static final long serialVersionUID = 6165693342087329096L;
19
-
20
- private final ItemType trackedItem;
21
-
22
- /** @see TrackImpl#TrackImpl(String) */
23
- public MappedTrackImpl(ItemType trackedItem, String nameForReadWriteLock) {
24
- super(nameForReadWriteLock);
25
- this.trackedItem = trackedItem;
26
- }
27
-
28
- /** @see TrackImpl#TrackImpl(ArrayListNavigableSet, String) */
29
- protected MappedTrackImpl(ItemType trackedItem, ArrayListNavigableSet<Timed> fixes, String nameForReadWriteLock) {
30
- super(fixes, nameForReadWriteLock);
31
- this.trackedItem = trackedItem;
32
- }
33
-
34
- @Override
35
- public ItemType getTrackedItem() {
36
- return trackedItem;
37
- }
38
-
39
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/PartialNavigableSetView.java
... ...
@@ -1,387 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import java.util.ArrayList;
4
-import java.util.Collection;
5
-import java.util.Comparator;
6
-import java.util.Iterator;
7
-import java.util.List;
8
-import java.util.NavigableSet;
9
-import java.util.NoSuchElementException;
10
-import java.util.SortedSet;
11
-import java.util.TreeSet;
12
-
13
-/**
14
- * A view on a {@link NavigableSet} which suppresses some entries based on some configurable rule.
15
- * The {@link #size()} operation is expensive because it requires a full scan. {@link #isEmpty()} is
16
- * much cheaper because it suffices to find one element passing the filter rule. The filtering rule
17
- * has to be expressed by subclsses implementing the {@link #isValid(Object)} method.
18
- *
19
- * @author Axel Uhl (d043530)
20
- *
21
- * @param <E>
22
- */
23
-public abstract class PartialNavigableSetView<E> implements NavigableSet<E> {
24
- private final NavigableSet<E> set;
25
-
26
- private class PartialNavigableSetViewWithSameValidityAsEnclosing extends PartialNavigableSetView<E> {
27
- public PartialNavigableSetViewWithSameValidityAsEnclosing(NavigableSet<E> set) {
28
- super(set);
29
- }
30
-
31
- @Override
32
- protected boolean isValid(E e) {
33
- return PartialNavigableSetView.this.isValid(e);
34
- }
35
- }
36
-
37
- private class FilteringIterator implements Iterator<E> {
38
- /**
39
- * The iterator is always kept one step "ahead" in order to know whether there really is a next element. The
40
- * next valid element is fetched and stored in {@link #nextValid} and {@link #hasNext} is set to
41
- * <code>true</code>.
42
- */
43
- private Iterator<E> iter;
44
-
45
- private E nextValid;
46
-
47
- private boolean hasNext;
48
-
49
- private boolean hasLastNext;
50
-
51
- private E lastNext;
52
-
53
- public FilteringIterator() {
54
- iter = getSet().iterator();
55
- hasLastNext = false;
56
- advance();
57
- }
58
-
59
- private void advance() {
60
- if (iter.hasNext()) {
61
- E next = iter.next();
62
- while (!isValid(next) && iter.hasNext()) {
63
- next = iter.next();
64
- }
65
- if (isValid(next)) {
66
- nextValid = next;
67
- hasNext = true;
68
- } else {
69
- hasNext = false;
70
- }
71
- } else {
72
- hasNext = false;
73
- }
74
- }
75
-
76
- @Override
77
- public boolean hasNext() {
78
- return hasNext;
79
- }
80
-
81
- @Override
82
- public E next() {
83
- if (hasNext) {
84
- E result = nextValid;
85
- advance();
86
- hasLastNext = true;
87
- lastNext = result;
88
- return result;
89
- } else {
90
- throw new NoSuchElementException();
91
- }
92
- }
93
-
94
- @Override
95
- public void remove() {
96
- if (!hasLastNext) {
97
- throw new IllegalStateException("next() was not called before remove()");
98
- } else {
99
- PartialNavigableSetView.this.remove(lastNext);
100
- hasLastNext = false;
101
- }
102
- }
103
- }
104
-
105
- public PartialNavigableSetView(NavigableSet<E> set) {
106
- this.set = set;
107
- }
108
-
109
- /**
110
- * Subclasses need to implement this method. For elements to be eliminated from the view represented by this
111
- * object, return <code>false</code> for such an element being passed to this method.
112
- */
113
- abstract protected boolean isValid(E e);
114
-
115
- @Override
116
- public Comparator<? super E> comparator() {
117
- return getSet().comparator();
118
- }
119
-
120
- public NavigableSet<E> descendingSet() {
121
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().descendingSet());
122
- }
123
-
124
- @Override
125
- public Iterator<E> descendingIterator() {
126
- return descendingSet().iterator();
127
- }
128
-
129
- @Override
130
- public E first() {
131
- E first = getSet().first();
132
- while (first != null && !isValid(first)) {
133
- first = getSet().higher(first);
134
- }
135
- if (first == null) {
136
- throw new NoSuchElementException();
137
- } else {
138
- return first;
139
- }
140
- }
141
-
142
- @Override
143
- public E last() {
144
- E last = getSet().last();
145
- while (last != null && !isValid(last)) {
146
- last = getSet().lower(last);
147
- }
148
- if (last == null) {
149
- throw new NoSuchElementException();
150
- } else {
151
- return last;
152
- }
153
- }
154
-
155
- @Override
156
- public int size() {
157
- int size = 0;
158
- for (E e : getSet()) {
159
- if (isValid(e)) {
160
- size++;
161
- }
162
- }
163
- return size;
164
- }
165
-
166
- @Override
167
- public boolean isEmpty() {
168
- for (E e : getSet()) {
169
- if (isValid(e)) {
170
- return false;
171
- }
172
- }
173
- return true;
174
- }
175
-
176
- @SuppressWarnings("unchecked")
177
- @Override
178
- public boolean contains(Object o) {
179
- return getSet().contains(o) && isValid((E) o);
180
- }
181
-
182
- @Override
183
- public Object[] toArray() {
184
- List<E> l = new ArrayList<E>();
185
- for (E e : getSet()) {
186
- if (isValid(e)) {
187
- l.add(e);
188
- }
189
- }
190
- return l.toArray();
191
- }
192
-
193
- @SuppressWarnings("unchecked")
194
- @Override
195
- public <T> T[] toArray(T[] a) {
196
- List<T> l = new ArrayList<T>();
197
- for (E e : getSet()) {
198
- if (isValid(e)) {
199
- l.add((T) e);
200
- }
201
- }
202
- return l.toArray(a);
203
-
204
- }
205
-
206
- @Override
207
- public boolean add(E e) {
208
- return getSet().add(e);
209
- }
210
-
211
- @Override
212
- public boolean remove(Object o) {
213
- return getSet().remove(o);
214
- }
215
-
216
- @SuppressWarnings("unchecked")
217
- @Override
218
- public boolean containsAll(Collection<?> c) {
219
- for (Object o : c) {
220
- if (!isValid((E) o) || !getSet().contains(o)) {
221
- return false;
222
- }
223
- }
224
- return true;
225
- }
226
-
227
- @Override
228
- public boolean addAll(Collection<? extends E> c) {
229
- return getSet().addAll(c);
230
- }
231
-
232
- @Override
233
- public boolean retainAll(Collection<?> c) {
234
- return getSet().retainAll(c);
235
- }
236
-
237
- @Override
238
- public boolean removeAll(Collection<?> c) {
239
- return getSet().removeAll(c);
240
- }
241
-
242
- @Override
243
- public void clear() {
244
- getSet().clear();
245
- }
246
-
247
- @Override
248
- public E lower(E e) {
249
- E result = getSet().lower(e);
250
- while (result != null && !isValid(result)) {
251
- result = getSet().lower(result);
252
- }
253
- return result;
254
- }
255
-
256
- /**
257
- * goes one left on the raw, unfiltered set and therefore may return fixes that have {@link #isValid(Object)}==
258
- * <code>false</code>
259
- */
260
- protected E lowerInternal(E e) {
261
- return getSet().lower(e);
262
- }
263
-
264
- /**
265
- * goes one right on the raw, unfiltered set and therefore may return fixes that have {@link #isValid(Object)}==
266
- * <code>false</code>
267
- */
268
- protected E higherInternal(E e) {
269
- return getSet().higher(e);
270
- }
271
-
272
- @Override
273
- public E floor(E e) {
274
- E result = getSet().floor(e);
275
- while (result != null && !isValid(result)) {
276
- result = getSet().lower(result);
277
- }
278
- return result;
279
- }
280
-
281
- @Override
282
- public E ceiling(E e) {
283
- E result = getSet().ceiling(e);
284
- while (result != null && !isValid(result)) {
285
- result = getSet().higher(result);
286
- }
287
- return result;
288
- }
289
-
290
- @Override
291
- public E higher(E e) {
292
- E result = getSet().higher(e);
293
- while (result != null && !isValid(result)) {
294
- result = getSet().higher(result);
295
- }
296
- return result;
297
- }
298
-
299
- /**
300
- * Removes all raw fixes that have {@link #isValid(Object)}==<code>false</code> and the first element to have
301
- * {@link #isValid(Object)}==<code>true</code>. This latter element is returned. If no such element exists,
302
- * <code>null</code> is returned. It is hence possible that invalid raw fixes are removed but stil <code>null</code>
303
- * is returned.
304
- */
305
- @Override
306
- public E pollFirst() {
307
- E result = getSet().first();
308
- while (result != null && !isValid(result)) {
309
- getSet().remove(result);
310
- result = getSet().first();
311
- }
312
- return result;
313
- }
314
-
315
- @Override
316
- public E pollLast() {
317
- E result = getSet().last();
318
- while (result != null && !isValid(result)) {
319
- getSet().remove(result);
320
- result = getSet().last();
321
- }
322
- return result;
323
- }
324
-
325
- @Override
326
- public NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) {
327
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().subSet(fromElement, fromInclusive, toElement, toInclusive));
328
- }
329
-
330
- @Override
331
- public NavigableSet<E> headSet(E toElement, boolean inclusive) {
332
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().headSet(toElement, inclusive));
333
- }
334
-
335
- @Override
336
- public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
337
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(getSet().tailSet(fromElement, inclusive));
338
- }
339
-
340
- @Override
341
- public NavigableSet<E> subSet(E fromElement, E toElement) {
342
- SortedSet<E> subSet = set.subSet(fromElement, toElement);
343
- if (subSet instanceof NavigableSet<?>) {
344
- return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) subSet);
345
- } else {
346
- TreeSet<E> result = new TreeSet<E>(subSet);
347
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
348
- }
349
- }
350
-
351
- @Override
352
- public NavigableSet<E> headSet(E toElement) {
353
- SortedSet<E> headSet = set.headSet(toElement);
354
- if (headSet instanceof NavigableSet<?>) {
355
- return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) headSet);
356
- } else {
357
- TreeSet<E> result = new TreeSet<E>(headSet);
358
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
359
- }
360
- }
361
-
362
- @Override
363
- public NavigableSet<E> tailSet(E fromElement) {
364
- SortedSet<E> tailSet = set.tailSet(fromElement);
365
- if (tailSet instanceof NavigableSet<?>) {
366
- return new PartialNavigableSetViewWithSameValidityAsEnclosing((NavigableSet<E>) tailSet);
367
- } else {
368
- TreeSet<E> result = new TreeSet<E>(tailSet);
369
- return new PartialNavigableSetViewWithSameValidityAsEnclosing(result);
370
- }
371
- }
372
-
373
-
374
- @Override
375
- public Iterator<E> iterator() {
376
- return new FilteringIterator();
377
- }
378
-
379
- @Override
380
- public String toString() {
381
- return new ArrayList<E>(this).toString();
382
- }
383
-
384
- protected NavigableSet<E> getSet() {
385
- return set;
386
- }
387
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TimeRangeCache.java
... ...
@@ -1,221 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import java.util.Comparator;
4
-import java.util.Iterator;
5
-import java.util.LinkedHashMap;
6
-import java.util.Map.Entry;
7
-import java.util.NavigableSet;
8
-
9
-import com.sap.sse.common.TimePoint;
10
-import com.sap.sse.common.Util;
11
-import com.sap.sse.common.Util.Pair;
12
-import com.sap.sse.concurrent.LockUtil;
13
-import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
14
-import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
15
-
16
-/**
17
- * This cache looks "backwards." It contains pairs whose first component represents a <code>to</code> parameter used in
18
- * a calculation for a time range. It is ordered by this component. The second component is a navigable, ordered set of
19
- * pairs where the first pair component represents a <code>from</code> parameter used in the calculation's time range
20
- * and the second pair component represents the result of the calculation for this parameter combination.
21
- * <p>
22
- *
23
- * For implementation efficiency in combination with using an {@link ArrayListNavigableSet} for the values and in order
24
- * to be able to efficiently extend a cache entry for a single <code>to</code> fix, the navigable sets containing the
25
- * <code>from</code> fixes and results are ordered such that earlier fixes come later in the set. This way, extending
26
- * the cache entry for a <code>to</code> fix to an earlier <code>from</code> fix only requires appending to the set.
27
- * <p>
28
- *
29
- * <b>Invalidation</b>: When a new fix is added to the track, all cache entries for fixes at or later than the new fix's
30
- * time point are removed from this cache. Additionally, the fix insertion may have an impact on the
31
- * {@link #getEarliestFromAndResultAtOrAfterFrom(TimePoint, TimePoint) previous fix's} validity (track smoothing) and
32
- * therefore on its selection for result aggregation. Therefore, if fix addition turned the previous fix invalid, the
33
- * cache entries for the time points at or after the previous fix also need to be removed.
34
- * <p>
35
- *
36
- * <b>Cache use</b>: When a result across a time range is to be computed the calculating method should first look for a
37
- * cache entry for the <code>to</code> parameter. If one is found, the earliest entry in the navigable set for the
38
- * navigable set of <code>from</code> and result values that is at or after the requested <code>from</code> time point
39
- * is determined. If such an entry exists, the result is remembered and the algorithm is repeated recursively, using the
40
- * <code>from</code> value found in the cache as the new <code>to</code> value, and the <code>from</code> value
41
- * originally passed to the calculating method as <code>from</code> again. If no entry is found in the cache entry for
42
- * <code>to</code> that is at or after the requested <code>from</code> time, the result has to be computed "from
43
- * scratch."
44
- * <p>
45
- *
46
- * If a cache entry for <code>to</code> is not found, the latest cache entry before it is looked up. If one is found,
47
- * the result for the time range between the <code>to</code> time point requested and the <code>to</code> time point
48
- * found in the cache is computed by iterating the smoothened fixes for this interval. If none is found, the result is
49
- * computed by iterating backwards all the way to <code>from</code>.
50
- * <p>
51
- *
52
- * Once the calculating method has computed its value, it should {@link #cache(TimePoint, TimePoint, Object) add} the
53
- * result to the cache.
54
- *
55
- * @author Axel Uhl (D043530)
56
- */
57
-public class TimeRangeCache<T> {
58
- public static final int MAX_SIZE = 100;
59
-
60
- private final NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> timeRangeCache;
61
-
62
- /**
63
- * The cache is to have limited size. Eviction shall happen based on a least-recently-used strategy. Usage is
64
- * defined as having been returned by {@link #getEarliestFromAndResultAtOrAfterFrom(TimePoint, TimePoint)} or
65
- * having been added by {@link #cache(TimePoint, TimePoint, Object)}.
66
- * <p>
67
- *
68
- * When an eldest entry is asked to be expunged from this map and the map has more than {@link #MAX_SIZE} elements,
69
- * the expunging will be admitted, and the entry is removed from the {@link #timeRangeCache} core structure. Reading
70
- * and writing this structure must happen under the {@link #lock write lock} because also reading the linked hash
71
- * map that counts access as "use" has a modifying effect on its internal structures.
72
- * <p>
73
- *
74
- * The key pairs are from/to pairs. Note that this is in some sense "the opposite direction" compared to the
75
- * alignment of the {@link #timeRangeCache} structure which has as its outer keys the "to" time point.<p>
76
- *
77
- * Read access is to be <code>synchronized<code> using this field's mutex; write access only happens under the
78
- * {@link #lock write lock} and therefore will have no contenders.
79
- */
80
- private final LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void> lruCache;
81
-
82
- private final NamedReentrantReadWriteLock lock;
83
-
84
- private static final Comparator<Util.Pair<TimePoint, ?>> timePointInPairComparator = new Comparator<Util.Pair<TimePoint, ?>>() {
85
- @Override
86
- public int compare(Util.Pair<TimePoint, ?> o1, Util.Pair<TimePoint, ?> o2) {
87
- return o1.getA().compareTo(o2.getA());
88
- }
89
- };
90
-
91
- public TimeRangeCache(String nameForLockLogging) {
92
- lock = new NamedReentrantReadWriteLock("lock for TimeRangeCache for "+nameForLockLogging, /* fair */ true);
93
- this.timeRangeCache = new ArrayListNavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>>(timePointInPairComparator);
94
- this.lruCache = new LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void>(/* initial capacity */ 10, /* load factor */ 0.75f,
95
- /* access-based ordering */ true) {
96
- private static final long serialVersionUID = -6568235517111733193L;
97
-
98
- @Override
99
- protected boolean removeEldestEntry(Entry<Pair<TimePoint, TimePoint>, Void> eldest) {
100
- final boolean expunge = size() > MAX_SIZE;
101
- if (expunge) {
102
- removeCacheEntry(eldest.getKey().getA(), eldest.getKey().getB());
103
- }
104
- return expunge;
105
- }
106
- };
107
- }
108
-
109
- public int size() {
110
- return lruCache.size();
111
- }
112
-
113
- private void removeCacheEntry(TimePoint from, TimePoint to) {
114
- assert lock.getWriteHoldCount() == 1; // we can be sure we are alone here; this only happens when adding a new entry, holding the write lock
115
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(createDummy(to));
116
- if (entryForTo.getA().equals(to)) {
117
- Pair<TimePoint, T> entryForFrom = entryForTo.getB().ceiling(new Util.Pair<TimePoint, T>(from, null));
118
- if (entryForFrom.getA().equals(from)) {
119
- entryForTo.getB().remove(entryForFrom);
120
- if (entryForTo.getB().isEmpty()) {
121
- timeRangeCache.remove(entryForTo);
122
- }
123
- }
124
- }
125
- }
126
-
127
- /**
128
- * Looks up the entry closest to but no later than <code>to</code>. If not found, <code>null</code> is returned. If
129
- * found, the earliest pair of from/result that is at or after <code>from</code> will be returned, together with
130
- * the <code>to</code> value of the entry. If there is no entry that is at or after <code>from</code>,
131
- * <code>null</code> is returned.
132
- */
133
- public Util.Pair<TimePoint, Util.Pair<TimePoint, T>> getEarliestFromAndResultAtOrAfterFrom(TimePoint from, TimePoint to) {
134
- LockUtil.lockForRead(lock);
135
- try {
136
- Util.Pair<TimePoint, Util.Pair<TimePoint, T>> result = null;
137
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(createDummy(to));
138
- if (entryForTo != null) {
139
- final Util.Pair<TimePoint, T> fromCeiling = entryForTo.getB().ceiling(new Util.Pair<TimePoint, T>(from, null));
140
- if (fromCeiling != null) {
141
- result = new Util.Pair<TimePoint, Util.Pair<TimePoint, T>>(entryForTo.getA(), fromCeiling);
142
- }
143
- }
144
- // no writer can be active because we're holding the read lock; read access on the lruCache is synchronized using
145
- // the lruCache's mutex; this is necessary because we're using access-based LRU pinging where even getting an entry
146
- // modifies the internal parts of the data structure which is not thread safe.
147
- synchronized (lruCache) { // ping the "perfect match" although it may not even have existed in the cache
148
- lruCache.get(new Util.Pair<TimePoint, TimePoint>(from, to));
149
- }
150
- return result;
151
- } finally {
152
- LockUtil.unlockAfterRead(lock);
153
- }
154
- }
155
-
156
- /**
157
- * Removes all cache entries that have a <code>to</code> time point that is at or after <code>timePoint</code>.
158
- */
159
- public void invalidateAllAtOrLaterThan(TimePoint timePoint) {
160
- LockUtil.lockForWrite(lock);
161
- try {
162
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> dummy = createDummy(timePoint);
163
- NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> toRemove = timeRangeCache.tailSet(dummy, /* inclusive */ true);
164
- for (Iterator<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> i=toRemove.iterator(); i.hasNext(); ) {
165
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryToRemove = i.next();
166
- assert entryToRemove.getA().compareTo(timePoint) >= 0;
167
- for (Pair<TimePoint, T> fromAndResult : entryToRemove.getB()) {
168
- lruCache.remove(new Util.Pair<>(fromAndResult.getA(), entryToRemove.getA()));
169
- }
170
- i.remove();
171
- }
172
- } finally {
173
- LockUtil.unlockAfterWrite(lock);
174
- }
175
- }
176
-
177
- private NavigableSet<Util.Pair<TimePoint, T>> getEntryForTo(TimePoint to) {
178
- NavigableSet<Util.Pair<TimePoint, T>> result = null;
179
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> dummyForTo = createDummy(to);
180
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(dummyForTo);
181
- if (entryForTo != null && entryForTo.getA().equals(to)) {
182
- result = entryForTo.getB();
183
- }
184
- return result;
185
- }
186
-
187
- public void cache(TimePoint from, TimePoint to, T result) {
188
- LockUtil.lockForWrite(lock);
189
- try {
190
- NavigableSet<Util.Pair<TimePoint, T>> entryForTo = getEntryForTo(to);
191
- if (entryForTo == null) {
192
- entryForTo = new ArrayListNavigableSet<Util.Pair<TimePoint, T>>(timePointInPairComparator);
193
- Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> pairForTo = new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>(
194
- to, entryForTo);
195
- timeRangeCache.add(pairForTo);
196
- }
197
- entryForTo.add(new Util.Pair<TimePoint, T>(from, result));
198
- lruCache.put(new Util.Pair<TimePoint, TimePoint>(from, to), null);
199
- } finally {
200
- LockUtil.unlockAfterWrite(lock);
201
- }
202
- }
203
-
204
- private Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> createDummy(TimePoint to) {
205
- return new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>(to, null);
206
- }
207
-
208
- /**
209
- * Removes all contents from this cache
210
- */
211
- public void clear() {
212
- LockUtil.lockForWrite(lock);
213
- try {
214
- timeRangeCache.clear();
215
- lruCache.clear();
216
- } finally {
217
- LockUtil.unlockAfterWrite(lock);
218
- }
219
-
220
- }
221
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TimedComparator.java
... ...
@@ -1,17 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import java.io.Serializable;
4
-import java.util.Comparator;
5
-
6
-import com.sap.sse.common.Timed;
7
-
8
-public class TimedComparator implements Comparator<Timed>, Serializable {
9
- private static final long serialVersionUID = 1604511471599854988L;
10
- public static final Comparator<Timed> INSTANCE = new TimedComparator();
11
-
12
- @Override
13
- public int compare(Timed o1, Timed o2) {
14
- return o1.getTimePoint().compareTo(o2.getTimePoint());
15
- }
16
-}
17
-
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java
... ...
@@ -1,544 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import java.io.IOException;
4
-import java.io.ObjectOutputStream;
5
-import java.util.ConcurrentModificationException;
6
-import java.util.Iterator;
7
-import java.util.NavigableSet;
8
-import java.util.function.Function;
9
-
10
-import com.sap.sailing.domain.tracking.AddResult;
11
-import com.sap.sailing.domain.tracking.FixAcceptancePredicate;
12
-import com.sap.sailing.domain.tracking.Track;
13
-import com.sap.sse.common.Duration;
14
-import com.sap.sse.common.TimePoint;
15
-import com.sap.sse.common.Timed;
16
-import com.sap.sse.common.Util;
17
-import com.sap.sse.common.Util.Pair;
18
-import com.sap.sse.common.scalablevalue.ScalableValue;
19
-import com.sap.sse.concurrent.LockUtil;
20
-import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
21
-import com.sap.sse.shared.util.impl.ArrayListNavigableSet;
22
-import com.sap.sse.shared.util.impl.UnmodifiableNavigableSet;
23
-
24
-public class TrackImpl<FixType extends Timed> implements Track<FixType> {
25
- private static final long serialVersionUID = -4075853657857657528L;
26
- /**
27
- * The fixes, ordered by their time points
28
- */
29
- private final ArrayListNavigableSet<Timed> fixes;
30
-
31
- private final NamedReentrantReadWriteLock readWriteLock;
32
-
33
- protected static class DummyTimed implements Timed {
34
- private static final long serialVersionUID = 6047311973718918856L;
35
- private final TimePoint timePoint;
36
- public DummyTimed(TimePoint timePoint) {
37
- super();
38
- this.timePoint = timePoint;
39
- }
40
- @Override
41
- public TimePoint getTimePoint() {
42
- return timePoint;
43
- }
44
- @Override
45
- public String toString() {
46
- return timePoint.toString();
47
- }
48
- }
49
-
50
- public TrackImpl(String nameForReadWriteLock) {
51
- this(new ArrayListNavigableSet<Timed>(TimedComparator.INSTANCE), nameForReadWriteLock);
52
- }
53
-
54
- protected TrackImpl(ArrayListNavigableSet<Timed> fixes, String nameForReadWriteLock) {
55
- this.readWriteLock = new NamedReentrantReadWriteLock(nameForReadWriteLock, /* fair */ false);
56
- this.fixes = fixes;
57
- }
58
-
59
- /**
60
- * Synchronize the serialization such that no fixes are added while serializing
61
- */
62
- private void writeObject(ObjectOutputStream s) throws IOException {
63
- lockForRead();
64
- try {
65
- s.defaultWriteObject();
66
- } finally {
67
- unlockAfterRead();
68
- }
69
- }
70
-
71
- @Override
72
- public void lockForRead() {
73
- LockUtil.lockForRead(readWriteLock);
74
- }
75
-
76
- @Override
77
- public void unlockAfterRead() {
78
- LockUtil.unlockAfterRead(readWriteLock);
79
- }
80
-
81
- protected void lockForWrite() {
82
- LockUtil.lockForWrite(readWriteLock);
83
- }
84
-
85
- protected void unlockAfterWrite() {
86
- LockUtil.unlockAfterWrite(readWriteLock);
87
- }
88
-
89
- /**
90
- * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and {@link #unlockAfterRead()}
91
- * to avoid {@link ConcurrentModificationException}s. Should they modify the structure returned, they have to use
92
- * {@link #lockForWrite()} and {@link #unlockAfterWrite()}, respectively.
93
- */
94
- protected NavigableSet<FixType> getInternalRawFixes() {
95
- @SuppressWarnings("unchecked")
96
- NavigableSet<FixType> result = (NavigableSet<FixType>) fixes;
97
- return result;
98
- }
99
-
100
- /**
101
- * asserts that the calling thread holds at least one of read and write lock
102
- */
103
- protected void assertReadLock() {
104
- if (readWriteLock.getReadHoldCount() < 1 && readWriteLock.getWriteHoldCount() < 1) {
105
- throw new IllegalStateException("Caller must obtain read lock using lockForRead() before calling this method");
106
- }
107
- }
108
-
109
- protected void assertWriteLock() {
110
- if (readWriteLock.getWriteHoldCount() < 1) {
111
- throw new IllegalStateException("Caller must obtain write lock using lockForWrite() before calling this method");
112
- }
113
- }
114
-
115
- /**
116
- * Callers that want to iterate over the collection returned need to use {@link #lockForRead()} and
117
- * {@link #unlockAfterRead()} to avoid {@link ConcurrentModificationException}s.
118
- *
119
- * @return the smoothened fixes ordered by their time points; this implementation simply delegates to
120
- * {@link #getInternalRawFixes()} because for only {@link Timed} fixes we can't know how to remove outliers.
121
- * Subclasses that constrain the <code>FixType</code> may provide smoothening implementations.
122
- */
123
- protected NavigableSet<FixType> getInternalFixes() {
124
- NavigableSet<FixType> result = getInternalRawFixes();
125
- return result;
126
- }
127
-
128
- /**
129
- * Iterates the fixes with outliers getting skipped, in the order of their time points.
130
- * Relies on {@link #getInternalFixes()} to void the track view from outliers.
131
- */
132
- @Override
133
- public NavigableSet<FixType> getFixes() {
134
- assertReadLock();
135
- return new UnmodifiableNavigableSet<FixType>(getInternalFixes());
136
- }
137
-
138
- @Override
139
- public Iterable<FixType> getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive) {
140
- return getFixes().subSet(getDummyFix(from), fromInclusive, getDummyFix(to), toInclusive);
141
- }
142
-
143
- /**
144
- * Iterates over the raw sequence of fixes, all potential outliers included
145
- */
146
- @Override
147
- public NavigableSet<FixType> getRawFixes() {
148
- assertReadLock();
149
- return new UnmodifiableNavigableSet<FixType>(getInternalRawFixes());
150
- }
151
-
152
- @Override
153
- public FixType getLastFixAtOrBefore(TimePoint timePoint) {
154
- return getLastFixAtOrBefore(timePoint, /* fixAcceptancePredicate == null means accept all */ null);
155
- }
156
-
157
- private FixType getLastFixAtOrBefore(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
158
- lockForRead();
159
- try {
160
- final NavigableSet<FixType> headSet = getInternalFixes().headSet(getDummyFix(timePoint), /* inclusive */ true);
161
- for (final Iterator<FixType> i=headSet.descendingIterator(); i.hasNext(); ) {
162
- final FixType next = i.next();
163
- if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) {
164
- return next;
165
- }
166
- }
167
- return null;
168
- } finally {
169
- unlockAfterRead();
170
- }
171
- }
172
-
173
- @Override
174
- public FixType getLastFixBefore(TimePoint timePoint) {
175
- lockForRead();
176
- try {
177
- return (FixType) getInternalFixes().lower(getDummyFix(timePoint));
178
- } finally {
179
- unlockAfterRead();
180
- }
181
- }
182
-
183
- @Override
184
- public FixType getLastRawFixAtOrBefore(TimePoint timePoint) {
185
- lockForRead();
186
- try {
187
- return (FixType) getInternalRawFixes().floor(getDummyFix(timePoint));
188
- } finally {
189
- unlockAfterRead();
190
- }
191
- }
192
-
193
- @Override
194
- public FixType getFirstRawFixAtOrAfter(TimePoint timePoint) {
195
- lockForRead();
196
- try {
197
- return (FixType) getInternalRawFixes().ceiling(getDummyFix(timePoint));
198
- } finally {
199
- unlockAfterRead();
200
- }
201
- }
202
-
203
- @Override
204
- public FixType getFirstFixAtOrAfter(TimePoint timePoint) {
205
- return getFirstFixAtOrAfter(timePoint, /* fixAcceptancePredicate==null means accept all fixes */ null);
206
- }
207
-
208
- private FixType getFirstFixAtOrAfter(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
209
- lockForRead();
210
- try {
211
- final NavigableSet<FixType> tailSet = getInternalFixes().tailSet(getDummyFix(timePoint), /* inclusive */ true);
212
- for (final FixType next : tailSet) {
213
- if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) {
214
- return next;
215
- }
216
- }
217
- return null;
218
- } finally {
219
- unlockAfterRead();
220
- }
221
- }
222
-
223
- @Override
224
- public FixType getLastRawFixBefore(TimePoint timePoint) {
225
- lockForRead();
226
- try {
227
- return (FixType) getInternalRawFixes().lower(getDummyFix(timePoint));
228
- } finally {
229
- unlockAfterRead();
230
- }
231
- }
232
-
233
- @Override
234
- public FixType getFirstFixAfter(TimePoint timePoint) {
235
- lockForRead();
236
- try {
237
- return (FixType) getInternalFixes().higher(getDummyFix(timePoint));
238
- } finally {
239
- unlockAfterRead();
240
- }
241
- }
242
-
243
- @Override
244
- public FixType getFirstRawFixAfter(TimePoint timePoint) {
245
- lockForRead();
246
- try {
247
- return (FixType) getInternalRawFixes().higher(getDummyFix(timePoint));
248
- } finally {
249
- unlockAfterRead();
250
- }
251
- }
252
-
253
- @Override
254
- public FixType getFirstRawFix() {
255
- lockForRead();
256
- try {
257
- if (getInternalFixes().isEmpty()) {
258
- return null;
259
- } else {
260
- return (FixType) getInternalFixes().first();
261
- }
262
- } finally {
263
- unlockAfterRead();
264
- }
265
- }
266
-
267
- @Override
268
- public FixType getLastRawFix() {
269
- lockForRead();
270
- try {
271
- if (getInternalRawFixes().isEmpty()) {
272
- return null;
273
- } else {
274
- return (FixType) getInternalRawFixes().last();
275
- }
276
- } finally {
277
- unlockAfterRead();
278
- }
279
- }
280
-
281
- /**
282
- * @param fixAcceptancePredicate
283
- * if not {@code null}, adjacent fixes will be skipped as long as this predicate does not
284
- * {@link FixAcceptancePredicate#isAcceptFix(Object) accept} the fix. This can, e.g., be used to skip
285
- * fixes that don't have values in a dimension required. If {@code null}, the next fixes left and right
286
- * (including the exact {@code timePoint} if a fix exists there) will be used without further check.
287
- */
288
- private Pair<FixType, FixType> getSurroundingFixes(TimePoint timePoint, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
289
- FixType left = getLastFixAtOrBefore(timePoint, fixAcceptancePredicate);
290
- FixType right = getFirstFixAtOrAfter(timePoint, fixAcceptancePredicate);
291
- com.sap.sse.common.Util.Pair<FixType, FixType> result = new com.sap.sse.common.Util.Pair<>(left, right);
292
- return result;
293
- }
294
-
295
- private <V, T> T timeBasedAverage(TimePoint timePoint, ScalableValue<V, T> value1, TimePoint timePoint1, ScalableValue<V, T> value2, TimePoint timePoint2) {
296
- final T acc;
297
- if (timePoint1.equals(timePoint2)) {
298
- acc = value1.add(value2).divide(2);
299
- } else {
300
- long timeDiff1 = Math.abs(timePoint1.asMillis() - timePoint.asMillis());
301
- long timeDiff2 = Math.abs(timePoint2.asMillis() - timePoint.asMillis());
302
- acc = value1.multiply(timeDiff2).add(value2.multiply(timeDiff1)).divide(timeDiff1 + timeDiff2);
303
- }
304
- return acc;
305
- }
306
-
307
- @Override
308
- public <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
309
- Function<FixType, ScalableValue<InternalType, ValueType>> converter) {
310
- return getInterpolatedValue(timePoint, converter, /* fixAcceptancePredicate==null means accept all */ null);
311
- }
312
-
313
- protected <InternalType, ValueType> ValueType getInterpolatedValue(TimePoint timePoint,
314
- Function<FixType, ScalableValue<InternalType, ValueType>> converter, FixAcceptancePredicate<FixType> fixAcceptancePredicate) {
315
- final ValueType result;
316
- Pair<FixType, FixType> fixPair = getSurroundingFixes(timePoint, fixAcceptancePredicate);
317
- if (fixPair.getA() == null) {
318
- if (fixPair.getB() == null) {
319
- result = null;
320
- } else {
321
- result = converter.apply(fixPair.getB()).divide(1);
322
- }
323
- } else {
324
- if (fixPair.getB() == null || fixPair.getA() == fixPair.getB()) {
325
- result = converter.apply(fixPair.getA()).divide(1);
326
- } else {
327
- result = timeBasedAverage(timePoint,
328
- converter.apply(fixPair.getA()), fixPair.getA().getTimePoint(),
329
- converter.apply(fixPair.getB()), fixPair.getB().getTimePoint());
330
- }
331
- }
332
- return result;
333
- }
334
-
335
- @Override
336
- public Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive) {
337
- assertReadLock();
338
- return getTimeConstrainedFixesIterator(getInternalFixes(), startingAt, inclusive, /* endingAt */ null, /* endingAtInclusive */ false);
339
- }
340
-
341
- @Override
342
- public Iterator<FixType> getFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt,
343
- boolean endingAtInclusive) {
344
- assertReadLock();
345
- return getTimeConstrainedFixesIterator(getInternalFixes(), startingAt, startingAtInclusive, endingAt, endingAtInclusive);
346
- }
347
-
348
- @Override
349
- public Iterator<FixType> getFixesDescendingIterator(TimePoint startingAt, boolean inclusive) {
350
- assertReadLock();
351
- Iterator<FixType> result = (Iterator<FixType>) getInternalFixes().headSet(
352
- getDummyFix(startingAt), inclusive).descendingIterator();
353
- return result;
354
- }
355
-
356
- /**
357
- * Creates a dummy fix that conforms to <code>FixType</code>. This in particular means that subclasses
358
- * instantiating <code>FixType</code> with a specific class need to redefine this method so as to return
359
- * a dummy fix complying with their instantiation type used for <code>FixType</code>. Otherwise, a
360
- * {@link ClassCastException} may result upon certain operations performed with the fix returned by
361
- * this method.
362
- */
363
- protected FixType getDummyFix(TimePoint timePoint) {
364
- @SuppressWarnings("unchecked")
365
- FixType result = (FixType) new DummyTimed(timePoint);
366
- return result;
367
- }
368
-
369
- @Override
370
- public Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean inclusive) {
371
- assertReadLock();
372
- return getTimeConstrainedFixesIterator(getInternalRawFixes(), startingAt, inclusive, /* endingAt */ null, /* endingAtInclusive */ false);
373
- }
374
-
375
- private Iterator<FixType> getTimeConstrainedFixesIterator(NavigableSet<FixType> set, TimePoint startingAt, boolean startingAtInclusive,
376
- TimePoint endingAt, boolean endingAtInclusive) {
377
- assertReadLock();
378
- if (startingAt != null && endingAt != null) {
379
- set = set.subSet(getDummyFix(startingAt), startingAtInclusive, getDummyFix(endingAt), endingAtInclusive);
380
- } else if (endingAt != null) {
381
- set = set.headSet(getDummyFix(endingAt), endingAtInclusive);
382
- } else if (startingAt != null) {
383
- set = set.tailSet(getDummyFix(startingAt), startingAtInclusive);
384
- }
385
- Iterator<FixType> result = set.iterator();
386
- return result;
387
- }
388
-
389
- @Override
390
- public Iterator<FixType> getRawFixesIterator(TimePoint startingAt, boolean startingAtInclusive,
391
- TimePoint endingAt, boolean endingAtInclusive) {
392
- assertReadLock();
393
- return getTimeConstrainedFixesIterator(getInternalRawFixes(), startingAt, startingAtInclusive, endingAt, endingAtInclusive);
394
- }
395
-
396
- @Override
397
- public Iterator<FixType> getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive) {
398
- assertReadLock();
399
- Iterator<FixType> result = (Iterator<FixType>) getInternalRawFixes().headSet(
400
- getDummyFix(startingAt), inclusive).descendingIterator();
401
- return result;
402
- }
403
-
404
- protected boolean add(FixType fix) {
405
- return add(fix, /* replace */ false);
406
- }
407
-
408
- /**
409
- * @return {@code true} if the fix was added or replaced; {@code false} in case no change was performed
410
- */
411
- protected boolean add(FixType fix, boolean replace) {
412
- lockForWrite();
413
- try {
414
- final AddResult addResult = addWithoutLocking(fix, replace);
415
- return addResult == AddResult.ADDED || addResult == AddResult.REPLACED;
416
- } finally {
417
- unlockAfterWrite();
418
- }
419
- }
420
-
421
- /**
422
- * The caller must ensure to hold the write lock for this track when calling this method.
423
- *
424
- * @param replace
425
- * whether or not to replace an existing fix in the track that is equal to {@link #fix} as defined by the
426
- * comparator used for the {@link #fixes} set. By default this is a comparator only comparing the
427
- * fixes' time stamps. Subclasses may use different comparator implementations.
428
- */
429
- protected AddResult addWithoutLocking(FixType fix, boolean replace) {
430
- final AddResult result;
431
- final boolean added = getInternalRawFixes().add(fix);
432
- if (!added && replace) {
433
- getInternalRawFixes().remove(fix);
434
- result = getInternalRawFixes().add(fix) ? AddResult.REPLACED : AddResult.NOT_ADDED;
435
- } else {
436
- result = added ? AddResult.ADDED : AddResult.NOT_ADDED;
437
- }
438
- return result;
439
- }
440
-
441
- @Override
442
- public Duration getAverageIntervalBetweenFixes() {
443
- lockForRead();
444
- try {
445
- final Duration result;
446
- final int size = getRawFixes().size();
447
- if (size > 1) {
448
- result = getRawFixes().first().getTimePoint().until(getRawFixes().last().getTimePoint()).divide(size-1);
449
- } else {
450
- result = null;
451
- }
452
- return result;
453
- } finally {
454
- unlockAfterRead();
455
- }
456
- }
457
-
458
- @Override
459
- public Duration getAverageIntervalBetweenRawFixes() {
460
- lockForRead();
461
- try {
462
- final Duration result;
463
- final int size = getRawFixes().size();
464
- if (size > 1) {
465
- result = getRawFixes().first().getTimePoint().until(getRawFixes().last().getTimePoint()).divide(size-1);
466
- } else {
467
- result = null;
468
- }
469
- return result;
470
- } finally {
471
- unlockAfterRead();
472
- }
473
- }
474
-
475
- @Override
476
- public <T> T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator) {
477
- return getValueSumRecursively(from, to, /* recursionLevel */ 0, nullElement, adder, cache, valueCalculator);
478
- }
479
-
480
- private <T> T getValueSumRecursively(TimePoint from, TimePoint to, int recursionDepth, T nullElement,
481
- Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator) {
482
- T result;
483
- if (!from.before(to)) {
484
- result = nullElement;
485
- } else {
486
- boolean perfectCacheHit = false;
487
- lockForRead();
488
- try {
489
- Util.Pair<TimePoint, Util.Pair<TimePoint, T>> bestCacheEntry = cache.getEarliestFromAndResultAtOrAfterFrom(from, to);
490
- if (bestCacheEntry != null) {
491
- perfectCacheHit = true; // potentially a cache hit; but if it doesn't span the full interval, it's not perfect; see below
492
- // compute the missing stretches between best cache entry's "from" and our "from" and the cache
493
- // entry's "to" and our "to"
494
- T valueFromFromToBeginningOfCacheEntry = nullElement;
495
- T valueFromEndOfCacheEntryToTo = nullElement;
496
- if (!bestCacheEntry.getB().getA().equals(from)) {
497
- assert bestCacheEntry.getB().getA().after(from);
498
- perfectCacheHit = false;
499
- valueFromFromToBeginningOfCacheEntry = getValueSumRecursively(from, bestCacheEntry
500
- .getB().getA(), recursionDepth + 1, nullElement, adder, cache, valueCalculator);
501
- }
502
- if (!bestCacheEntry.getA().equals(to)) {
503
- assert bestCacheEntry.getA().before(to);
504
- perfectCacheHit = false;
505
- valueFromEndOfCacheEntryToTo = getValueSumRecursively(bestCacheEntry.getA(), to,
506
- recursionDepth + 1, nullElement, adder, cache, valueCalculator);
507
- }
508
- if (valueFromEndOfCacheEntryToTo == null || bestCacheEntry.getB().getB() == null) {
509
- result = null;
510
- } else {
511
- result = adder.add(adder.add(valueFromFromToBeginningOfCacheEntry, bestCacheEntry.getB().getB()),
512
- valueFromEndOfCacheEntryToTo);
513
- }
514
- } else {
515
- if (from.compareTo(to) < 0) {
516
- result = valueCalculator.calculate(from, to);
517
- } else {
518
- result = nullElement;
519
- }
520
- }
521
- // run the cache update while still holding the read lock; this avoids bug4629 where a cache invalidation
522
- // caused by fix insertions can come after the result calculation and before the cache update
523
- if (!perfectCacheHit && recursionDepth == 0) {
524
- cache.cache(from, to, result);
525
- }
526
- } finally {
527
- unlockAfterRead();
528
- }
529
- }
530
- return result;
531
- }
532
-
533
-
534
-
535
- @Override
536
- public int size() {
537
- return fixes.size();
538
- }
539
-
540
- @Override
541
- public boolean isEmpty() {
542
- return fixes.isEmpty();
543
- }
544
-}
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackingConnectorInfoImpl.java
... ...
@@ -1,66 +0,0 @@
1
-package com.sap.sailing.domain.tracking.impl;
2
-
3
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
4
-
5
-public class TrackingConnectorInfoImpl implements TrackingConnectorInfo {
6
- private static final long serialVersionUID = 7970268841592389145L;
7
- private final String trackingConnectorName;
8
- private final String TtackingConnectorDefaultUrl;
9
- private final String webUrl;
10
-
11
- public TrackingConnectorInfoImpl(String trackingConnectorName, String trackingConnectorDefaultUrl, String webUrl) {
12
- super();
13
- this.trackingConnectorName = trackingConnectorName;
14
- TtackingConnectorDefaultUrl = trackingConnectorDefaultUrl;
15
- this.webUrl = webUrl;
16
- }
17
-
18
- public String getTrackingConnectorDefaultUrl() {
19
- return TtackingConnectorDefaultUrl;
20
- }
21
-
22
- public String getTrackingConnectorName() {
23
- return trackingConnectorName;
24
- }
25
-
26
- public String getWebUrl() {
27
- return webUrl;
28
- }
29
-
30
- @Override
31
- public int hashCode() {
32
- final int prime = 31;
33
- int result = 1;
34
- result = prime * result + ((TtackingConnectorDefaultUrl == null) ? 0 : TtackingConnectorDefaultUrl.hashCode());
35
- result = prime * result + ((trackingConnectorName == null) ? 0 : trackingConnectorName.hashCode());
36
- result = prime * result + ((webUrl == null) ? 0 : webUrl.hashCode());
37
- return result;
38
- }
39
-
40
- @Override
41
- public boolean equals(Object obj) {
42
- if (this == obj)
43
- return true;
44
- if (obj == null)
45
- return false;
46
- if (getClass() != obj.getClass())
47
- return false;
48
- TrackingConnectorInfoImpl other = (TrackingConnectorInfoImpl) obj;
49
- if (TtackingConnectorDefaultUrl == null) {
50
- if (other.TtackingConnectorDefaultUrl != null)
51
- return false;
52
- } else if (!TtackingConnectorDefaultUrl.equals(other.TtackingConnectorDefaultUrl))
53
- return false;
54
- if (trackingConnectorName == null) {
55
- if (other.trackingConnectorName != null)
56
- return false;
57
- } else if (!trackingConnectorName.equals(other.trackingConnectorName))
58
- return false;
59
- if (webUrl == null) {
60
- if (other.webUrl != null)
61
- return false;
62
- } else if (!webUrl.equals(other.webUrl))
63
- return false;
64
- return true;
65
- }
66
-}
java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java
... ...
@@ -41,6 +41,7 @@ import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry
41 41
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
42 42
import com.sap.sailing.domain.racelog.RaceLogStore;
43 43
import com.sap.sailing.domain.regattalog.RegattaLogStore;
44
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
44 45
import com.sap.sailing.domain.swisstimingadapter.Course;
45 46
import com.sap.sailing.domain.swisstimingadapter.DomainFactory;
46 47
import com.sap.sailing.domain.swisstimingadapter.Fix;
... ...
@@ -70,7 +71,6 @@ import com.sap.sailing.domain.tracking.TrackingDataLoader;
70 71
import com.sap.sailing.domain.tracking.WindStore;
71 72
import com.sap.sailing.domain.tracking.WindTrack;
72 73
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
73
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
74 74
import com.sap.sailing.domain.tracking.impl.UpdateHandler;
75 75
import com.sap.sse.common.Distance;
76 76
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java
... ...
@@ -41,6 +41,7 @@ import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry
41 41
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
42 42
import com.sap.sailing.domain.racelog.RaceLogStore;
43 43
import com.sap.sailing.domain.regattalog.RegattaLogStore;
44
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
44 45
import com.sap.sailing.domain.swisstimingadapter.DomainFactory;
45 46
import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter;
46 47
import com.sap.sailing.domain.swisstimingreplayadapter.CompetitorStatus;
... ...
@@ -60,7 +61,6 @@ import com.sap.sailing.domain.tracking.WindTrack;
60 61
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
61 62
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
62 63
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
63
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
64 64
import com.sap.sse.common.Bearing;
65 65
import com.sap.sse.common.TimePoint;
66 66
import com.sap.sse.common.Util;
java/com.sap.sailing.domain.test/SailingDomainTest (No Proxy, Tunnel).launch
... ...
@@ -1,5 +1,6 @@
1 1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2 2
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
3
+ <booleanAttribute key="org.eclipse.debug.core.ATTR_FORCE_SYSTEM_CONSOLE_ENCODING" value="false"/>
3 4
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
4 5
<listEntry value="/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test"/>
5 6
</listAttribute>
... ...
@@ -10,7 +11,7 @@
10 11
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
11 12
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
12 13
</listAttribute>
13
- <stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.domain.test/src=/test=/true=/&lt;com.sap.sailing.domain.test"/>
14
+ <stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.domain.test/src&lt;com.sap.sailing.domain.test"/>
14 15
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
15 16
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
16 17
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
java/com.sap.sailing.domain.test/src/LeaderboardCourseChangeWithEliminationTest.java
... ...
@@ -1,14 +0,0 @@
1
-import com.sap.sailing.domain.base.Regatta;
2
-import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
3
-import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
4
-import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination;
5
-import com.sap.sailing.domain.test.LeaderboardCourseChangeTest;
6
-
7
-public class LeaderboardCourseChangeWithEliminationTest extends LeaderboardCourseChangeTest {
8
- @Override
9
- protected RegattaLeaderboardWithEliminations createRegattaLeaderboard(Regatta regatta,
10
- ThresholdBasedResultDiscardingRule discardingRule) {
11
- return new DelegatingRegattaLeaderboardWithCompetitorElimination(
12
- ()->super.createRegattaLeaderboard(regatta, discardingRule), "Test leaderboard with elimination");
13
- }
14
-}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/AbstractCandidateFilterTestSupport.java
... ...
@@ -12,7 +12,7 @@ import org.junit.jupiter.api.BeforeEach;
12 12
13 13
import com.sap.sailing.domain.base.Waypoint;
14 14
import com.sap.sailing.domain.markpassingcalculation.Candidate;
15
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
15
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
16 16
import com.sap.sse.common.Duration;
17 17
import com.sap.sse.common.TimePoint;
18 18
import com.sap.sse.common.Util;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BravoFixTrackFoiledDistanceCacheTest.java
... ...
@@ -25,11 +25,11 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
25 25
import com.sap.sailing.domain.common.tracking.impl.BravoExtendedFixImpl;
26 26
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
27 27
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
28
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
28 29
import com.sap.sailing.domain.tracking.BravoFixTrack;
29 30
import com.sap.sailing.domain.tracking.DynamicBravoFixTrack;
30 31
import com.sap.sailing.domain.tracking.impl.BravoFixTrackImpl;
31 32
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
32
-import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
33 33
import com.sap.sse.common.Distance;
34 34
import com.sap.sse.common.TimePoint;
35 35
import com.sap.sse.common.impl.DegreeBearingImpl;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/FetchTracksAndStoreLocallyTest.java
... ...
@@ -15,7 +15,7 @@ import com.sap.sailing.domain.base.Boat;
15 15
import com.sap.sailing.domain.base.Competitor;
16 16
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
17 17
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
18
-import com.sap.sailing.domain.tracking.AddResult;
18
+import com.sap.sailing.domain.shared.tracking.AddResult;
19 19
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
20 20
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
21 21
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeWithEliminationTest.java
... ...
@@ -0,0 +1,15 @@
1
+package com.sap.sailing.domain.test;
2
+
3
+import com.sap.sailing.domain.base.Regatta;
4
+import com.sap.sailing.domain.leaderboard.RegattaLeaderboardWithEliminations;
5
+import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
6
+import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination;
7
+
8
+public class LeaderboardCourseChangeWithEliminationTest extends LeaderboardCourseChangeTest {
9
+ @Override
10
+ protected RegattaLeaderboardWithEliminations createRegattaLeaderboard(Regatta regatta,
11
+ ThresholdBasedResultDiscardingRule discardingRule) {
12
+ return new DelegatingRegattaLeaderboardWithCompetitorElimination(
13
+ ()->super.createRegattaLeaderboard(regatta, discardingRule), "Test leaderboard with elimination");
14
+ }
15
+}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java
... ...
@@ -74,12 +74,12 @@ import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
74 74
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
75 75
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
76 76
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
77
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
77 78
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
78 79
import com.sap.sailing.domain.tracking.MarkPassing;
79 80
import com.sap.sailing.domain.tracking.TrackedRace;
80 81
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
81 82
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
82
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
83 83
import com.sap.sse.common.Duration;
84 84
import com.sap.sse.common.TimePoint;
85 85
import com.sap.sse.common.Util;
... ...
@@ -2918,16 +2918,10 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki
2918 2918
assertTrue(Util.indexOf(rankedCompetitors, lastRaceGoldParticipant) < Util.indexOf(rankedCompetitors, silverParticipant));
2919 2919
}
2920 2920
}
2921
- // assert that theUntrackedCompetitorInLastRace ended up between the last race's silver and gold fleet participants
2922
- // based on the "extreme fleet" rule:
2921
+ // assert that theUntrackedCompetitorInLastRace ended up ranked worse than all participants of ranked fleets:
2923 2922
for (final Competitor c : competitors) {
2924 2923
if (c != theUntrackedCompetitorInLastRace) {
2925
- if (lastRaceGold.contains(c) || medal.contains(c)) {
2926
- assertTrue(Util.indexOf(rankedCompetitors, c) < Util.indexOf(rankedCompetitors, theUntrackedCompetitorInLastRace));
2927
- } else {
2928
- assertTrue(silver.contains(c));
2929
- assertTrue(Util.indexOf(rankedCompetitors, c) > Util.indexOf(rankedCompetitors, theUntrackedCompetitorInLastRace));
2930
- }
2924
+ assertTrue(Util.indexOf(rankedCompetitors, c) < Util.indexOf(rankedCompetitors, theUntrackedCompetitorInLastRace));
2931 2925
}
2932 2926
}
2933 2927
}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTestBase.java
... ...
@@ -32,12 +32,12 @@ import com.sap.sailing.domain.leaderboard.ScoringScheme;
32 32
import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
33 33
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
34 34
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
35
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
35 36
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks;
36 37
import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndZeroRanks;
37 38
import com.sap.sailing.domain.tracking.MarkPassing;
38 39
import com.sap.sailing.domain.tracking.TrackedRace;
39 40
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
40
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
41 41
import com.sap.sse.common.Duration;
42 42
import com.sap.sse.common.TimePoint;
43 43
import com.sap.sse.common.impl.MillisecondsTimePoint;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LineAnalysisTest.java
... ...
@@ -14,9 +14,9 @@ import com.sap.sailing.domain.common.impl.MeterDistance;
14 14
import com.sap.sailing.domain.common.tracking.GPSFix;
15 15
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
16 16
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
17
+import com.sap.sailing.domain.shared.tracking.LineDetails;
17 18
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
18 19
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
19
-import com.sap.sailing.domain.tracking.LineDetails;
20 20
import com.sap.sse.common.Bearing;
21 21
import com.sap.sse.common.Distance;
22 22
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OnlineTracTracBasedTest.java
... ...
@@ -149,12 +149,9 @@ public abstract class OnlineTracTracBasedTest extends AbstractTracTracLiveTest i
149 149
case Begin:
150 150
logger.info("Stored data begin");
151 151
lastStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, 0);
152
- new Thread(()->{
153
- final RaceDefinition raceDefinition = domainFactory.getAndWaitForRaceDefinition(getTracTracRace().getId(), /* timeout in millis */ 10000);
154
- if (trackedRegatta != null && raceDefinition != null && trackedRegatta.getTrackedRace(raceDefinition) != null) {
155
- trackedRegatta.getTrackedRace(raceDefinition).onStatusChanged(OnlineTracTracBasedTest.this, lastStatus);
156
- }
157
- }, "waiting for race definition for race "+getTracTracRace().getId()).start();
152
+ if (getTrackedRace() != null) {
153
+ getTrackedRace().onStatusChanged(OnlineTracTracBasedTest.this, lastStatus);
154
+ }
158 155
break;
159 156
case End:
160 157
logger.info("Stored data end. Delaying status update on tracked race "+getTrackedRace()+" until all events queued in receivers so far have been processed");
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PartialNavigableSetViewTest.java
... ...
@@ -12,7 +12,7 @@ import java.util.TreeSet;
12 12
import org.junit.jupiter.api.BeforeEach;
13 13
import org.junit.jupiter.api.Test;
14 14
15
-import com.sap.sailing.domain.tracking.impl.PartialNavigableSetView;
15
+import com.sap.sailing.domain.shared.tracking.impl.PartialNavigableSetView;
16 16
17 17
public class PartialNavigableSetViewTest {
18 18
private PartialNavigableSetView<Integer> fullSet;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PassingInstructionParserTest.java
... ...
@@ -47,12 +47,11 @@ public class PassingInstructionParserTest extends OnlineTracTracBasedTest {
47 47
@Test
48 48
public void test() {
49 49
int i = 0;
50
- for(Waypoint w : getRace().getCourse().getWaypoints()){
51
- if(w.getPassingInstructions()==PassingInstruction.Starboard){
50
+ for (Waypoint w : getRace().getCourse().getWaypoints()) {
51
+ if (w.getPassingInstructions() == PassingInstruction.Starboard) {
52 52
i++;
53 53
}
54 54
}
55
- assertEquals(2,i);
55
+ assertEquals(2, i);
56 56
}
57
-
58 57
}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveTrackingDataTest.java
... ...
@@ -20,7 +20,7 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
20 20
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
21 21
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
22 22
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
23
-import com.sap.sailing.domain.tracking.AddResult;
23
+import com.sap.sailing.domain.shared.tracking.AddResult;
24 24
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
25 25
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
26 26
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ScoreCorrectionForUntrackedRacesTest.java
... ...
@@ -0,0 +1,112 @@
1
+package com.sap.sailing.domain.test;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertFalse;
4
+import static org.junit.jupiter.api.Assertions.assertTrue;
5
+import static org.mockito.Mockito.mock;
6
+import static org.mockito.Mockito.when;
7
+
8
+import java.util.Arrays;
9
+import java.util.UUID;
10
+
11
+import org.junit.jupiter.api.BeforeEach;
12
+import org.junit.jupiter.api.Test;
13
+
14
+import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
15
+import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
16
+import com.sap.sailing.domain.abstractlog.race.RaceLog;
17
+import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
18
+import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RegisteredCompetitorsAnalyzer;
19
+import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogRegisterCompetitorEventImpl;
20
+import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogUseCompetitorsFromRaceLogEventImpl;
21
+import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
22
+import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl;
23
+import com.sap.sailing.domain.base.CompetitorWithBoat;
24
+import com.sap.sailing.domain.base.Fleet;
25
+import com.sap.sailing.domain.base.RaceColumnInSeries;
26
+import com.sap.sailing.domain.base.Series;
27
+import com.sap.sailing.domain.base.impl.FleetImpl;
28
+import com.sap.sailing.domain.base.impl.SeriesImpl;
29
+import com.sap.sailing.domain.leaderboard.Leaderboard;
30
+import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
31
+import com.sap.sailing.domain.leaderboard.impl.ScoreCorrectionImpl;
32
+import com.sap.sse.common.TimePoint;
33
+import com.sap.sse.common.Util.Pair;
34
+
35
+/**
36
+ * See bug6168; tests fleet-specific check for score corrections in race columns that suggest that this
37
+ * fleet has finished the race.
38
+ *
39
+ * @author Axel Uhl (d043530)
40
+ *
41
+ */
42
+public class ScoreCorrectionForUntrackedRacesTest extends StoredTrackBasedTest {
43
+ private RaceColumnInSeries f1;
44
+ private RaceColumnInSeries f2;
45
+ private Series series;
46
+ private Fleet gold;
47
+ private Fleet silver;
48
+ private RegattaLog regattaLog;
49
+ private RaceLog goldRaceLogF1;
50
+ private RaceLog goldRaceLogF2;
51
+ private RaceLog silverRaceLogF1;
52
+ private RaceLog silverRaceLogF2;
53
+ private SettableScoreCorrection scoreCorrection;
54
+ private CompetitorWithBoat c1;
55
+ private CompetitorWithBoat c2;
56
+ private AbstractLogEventAuthor author;
57
+ private Leaderboard leaderboard;
58
+
59
+ @BeforeEach
60
+ public void setUp() {
61
+ leaderboard = mock(Leaderboard.class);
62
+ c1 = createCompetitorWithBoat("C1");
63
+ c2 = createCompetitorWithBoat("C2");
64
+ author = new LogEventAuthorImpl("Test", 1);
65
+ gold = new FleetImpl("Gold", 1);
66
+ silver = new FleetImpl("Silver", 2);
67
+ series = new SeriesImpl("Default", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(gold, silver), Arrays.asList("F1", "F2"), /* trackedRegattaRegistry */ null);
68
+ f1 = mock(RaceColumnInSeries.class);
69
+ when(f1.getName()).thenReturn("F1");
70
+ when(f1.getSeries()).thenReturn(series);
71
+ when(f1.getKey(c1)).thenReturn(new Pair<>(c1, f1));
72
+ when(f1.getKey(c2)).thenReturn(new Pair<>(c2, f1));
73
+ goldRaceLogF1 = new RaceLogImpl(UUID.randomUUID());
74
+ silverRaceLogF1 = new RaceLogImpl(UUID.randomUUID());
75
+ when(f1.getRaceLog(gold)).thenReturn(goldRaceLogF1);
76
+ when(f1.getRaceLog(silver)).thenReturn(silverRaceLogF1);
77
+ f2 = mock(RaceColumnInSeries.class);
78
+ when(f2.getName()).thenReturn("F2");
79
+ when(f2.getSeries()).thenReturn(series);
80
+ when(f2.getKey(c1)).thenReturn(new Pair<>(c1, f2));
81
+ when(f2.getKey(c2)).thenReturn(new Pair<>(c2, f2));
82
+ goldRaceLogF2 = new RaceLogImpl(UUID.randomUUID());
83
+ silverRaceLogF2 = new RaceLogImpl(UUID.randomUUID());
84
+ when(f2.getRaceLog(gold)).thenReturn(goldRaceLogF2);
85
+ when(f2.getRaceLog(silver)).thenReturn(silverRaceLogF2);
86
+ goldRaceLogF1.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0));
87
+ goldRaceLogF1.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c1));
88
+ goldRaceLogF2.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0));
89
+ goldRaceLogF2.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c1));
90
+ silverRaceLogF1.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0));
91
+ silverRaceLogF1.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c2));
92
+ silverRaceLogF2.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(TimePoint.now(), author, TimePoint.now(), UUID.randomUUID(), 0));
93
+ silverRaceLogF2.add(new RaceLogRegisterCompetitorEventImpl(TimePoint.now(), author, /* passId */ 0, c2));
94
+ regattaLog = new RegattaLogImpl(UUID.randomUUID());
95
+ when(f1.getAllCompetitors(gold)).thenReturn(new RegisteredCompetitorsAnalyzer(goldRaceLogF1, regattaLog).analyze());
96
+ when(f1.getAllCompetitors(silver)).thenReturn(new RegisteredCompetitorsAnalyzer(silverRaceLogF1, regattaLog).analyze());
97
+ when(f2.getAllCompetitors(gold)).thenReturn(new RegisteredCompetitorsAnalyzer(goldRaceLogF2, regattaLog).analyze());
98
+ when(f2.getAllCompetitors(silver)).thenReturn(new RegisteredCompetitorsAnalyzer(silverRaceLogF2, regattaLog).analyze());
99
+ scoreCorrection = new ScoreCorrectionImpl(leaderboard);
100
+ scoreCorrection.correctScore(c1, f1, 1.0);
101
+ scoreCorrection.correctScore(c1, f2, 1.0);
102
+ scoreCorrection.correctScore(c2, f1, 1.0);
103
+ }
104
+
105
+ @Test
106
+ public void testNumberOfRacesWithCorrections() {
107
+ assertTrue(scoreCorrection.hasCorrectionForNonTrackedFleet(f1, gold));
108
+ assertTrue(scoreCorrection.hasCorrectionForNonTrackedFleet(f1, silver));
109
+ assertTrue(scoreCorrection.hasCorrectionForNonTrackedFleet(f2, gold));
110
+ assertFalse(scoreCorrection.hasCorrectionForNonTrackedFleet(f2, silver));
111
+ }
112
+}
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StarbordSideOfStartLineRecognitionTest.java
... ...
@@ -26,8 +26,8 @@ import com.sap.sailing.domain.common.tracking.GPSFix;
26 26
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
27 27
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
28 28
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
29
+import com.sap.sailing.domain.shared.tracking.LineDetails;
29 30
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
30
-import com.sap.sailing.domain.tracking.LineDetails;
31 31
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
32 32
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
33 33
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java
... ...
@@ -44,14 +44,14 @@ import com.sap.sailing.domain.common.tracking.impl.CompactionNotPossibleExceptio
44 44
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
45 45
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
46 46
import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl;
47
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
48
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
47 49
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
48 50
import com.sap.sailing.domain.tracking.GPSFixTrack;
49 51
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
50 52
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
51 53
import com.sap.sailing.domain.tracking.impl.GPSFixTrackImpl;
52 54
import com.sap.sailing.domain.tracking.impl.MaxSpeedCache;
53
-import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
54
-import com.sap.sailing.domain.tracking.impl.TrackImpl;
55 55
import com.sap.sse.common.AbstractBearing;
56 56
import com.sap.sse.common.Bearing;
57 57
import com.sap.sse.common.Distance;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java
... ...
@@ -65,6 +65,8 @@ import com.sap.sailing.domain.ranking.RankingMetricConstructor;
65 65
import com.sap.sailing.domain.regattalike.IsRegattaLike;
66 66
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
67 67
import com.sap.sailing.domain.regattalike.RegattaLikeListener;
68
+import com.sap.sailing.domain.shared.tracking.LineDetails;
69
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
68 70
import com.sap.sailing.domain.tracking.CourseDesignChangedListener;
69 71
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
70 72
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
... ...
@@ -72,7 +74,6 @@ import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
72 74
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
73 75
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
74 76
import com.sap.sailing.domain.tracking.GPSFixTrack;
75
-import com.sap.sailing.domain.tracking.LineDetails;
76 77
import com.sap.sailing.domain.tracking.Maneuver;
77 78
import com.sap.sailing.domain.tracking.MarkPassing;
78 79
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
... ...
@@ -87,7 +88,6 @@ import com.sap.sailing.domain.tracking.TrackedLeg;
87 88
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
88 89
import com.sap.sailing.domain.tracking.TrackedRace;
89 90
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
90
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
91 91
import com.sap.sailing.domain.tracking.TrackingDataLoader;
92 92
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
93 93
import com.sap.sailing.domain.tracking.WindPositionMode;
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java
... ...
@@ -53,9 +53,10 @@ import com.sap.sailing.domain.leaderboard.impl.RankAndRankComparable;
53 53
import com.sap.sailing.domain.polars.PolarDataService;
54 54
import com.sap.sailing.domain.ranking.RankingMetric;
55 55
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
56
+import com.sap.sailing.domain.shared.tracking.LineDetails;
57
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
56 58
import com.sap.sailing.domain.tracking.CourseDesignChangedListener;
57 59
import com.sap.sailing.domain.tracking.GPSFixTrack;
58
-import com.sap.sailing.domain.tracking.LineDetails;
59 60
import com.sap.sailing.domain.tracking.Maneuver;
60 61
import com.sap.sailing.domain.tracking.MarkPassing;
61 62
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
... ...
@@ -69,7 +70,6 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
69 70
import com.sap.sailing.domain.tracking.TrackedRace;
70 71
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
71 72
import com.sap.sailing.domain.tracking.TrackedRegatta;
72
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
73 73
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
74 74
import com.sap.sailing.domain.tracking.WindPositionMode;
75 75
import com.sap.sailing.domain.tracking.WindStore;
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java
... ...
@@ -69,6 +69,8 @@ import com.sap.sailing.domain.racelog.RaceLogStore;
69 69
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
70 70
import com.sap.sailing.domain.ranking.RankingMetricsFactory;
71 71
import com.sap.sailing.domain.regattalog.RegattaLogStore;
72
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
73
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
72 74
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
73 75
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
74 76
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
... ...
@@ -78,13 +80,11 @@ import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
78 80
import com.sap.sailing.domain.tracking.RaceTrackingHandler;
79 81
import com.sap.sailing.domain.tracking.TrackedRegatta;
80 82
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
81
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
82 83
import com.sap.sailing.domain.tracking.WindStore;
83 84
import com.sap.sailing.domain.tracking.WindTrack;
84 85
import com.sap.sailing.domain.tracking.impl.FinishTimeUpdateHandler;
85 86
import com.sap.sailing.domain.tracking.impl.RaceAbortedHandler;
86 87
import com.sap.sailing.domain.tracking.impl.StartTimeUpdateHandler;
87
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
88 88
import com.sap.sailing.domain.tractracadapter.DomainFactory;
89 89
import com.sap.sailing.domain.tractracadapter.JSONService;
90 90
import com.sap.sailing.domain.tractracadapter.MetadataParser;
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java
... ...
@@ -23,13 +23,13 @@ import com.sap.sailing.domain.common.PassingInstruction;
23 23
import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
24 24
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
25 25
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
26
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
26 27
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
27 28
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
28 29
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
29 30
import com.sap.sailing.domain.tracking.RaceTrackingHandler;
30 31
import com.sap.sailing.domain.tracking.TrackedRace;
31 32
import com.sap.sailing.domain.tracking.WindStore;
32
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
33 33
import com.sap.sailing.domain.tractracadapter.DomainFactory;
34 34
import com.sap.sailing.domain.tractracadapter.TracTracAdapter;
35 35
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.domain.windfinderadapter/META-INF/MANIFEST.MF
... ...
@@ -7,7 +7,8 @@ Bundle-Version: 1.0.0.qualifier
7 7
Bundle-Activator: com.sap.sailing.domain.windfinderadapter.impl.Activator
8 8
Bundle-Vendor: SAP
9 9
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
10
-Import-Package: com.sap.sse.security,
10
+Import-Package: com.sap.sailing.domain.shared.tracking,
11
+ com.sap.sse.security,
11 12
org.osgi.framework;version="1.3.0",
12 13
org.osgi.util.tracker;version="1.5.1"
13 14
Bundle-ActivationPolicy: lazy
java/com.sap.sailing.domain.yellowbrickadapter/src/com/sap/sailing/domain/yellowbrickadapter/impl/YellowBrickRaceTrackerImpl.java
... ...
@@ -47,6 +47,7 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
47 47
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
48 48
import com.sap.sailing.domain.racelog.RaceLogStore;
49 49
import com.sap.sailing.domain.regattalog.RegattaLogStore;
50
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
50 51
import com.sap.sailing.domain.tracking.AbstractRaceTrackerImpl;
51 52
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
52 53
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
... ...
@@ -60,7 +61,6 @@ import com.sap.sailing.domain.tracking.WindStore;
60 61
import com.sap.sailing.domain.tracking.WindTrack;
61 62
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
62 63
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
63
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
64 64
import com.sap.sailing.domain.yellowbrickadapter.YellowBrickRace;
65 65
import com.sap.sailing.domain.yellowbrickadapter.YellowBrickRaceTrackingConnectivityParams;
66 66
import com.sap.sailing.domain.yellowbrickadapter.YellowBrickTrackingAdapter;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java
... ...
@@ -24,8 +24,8 @@ import com.sap.sailing.domain.common.WindSourceType;
24 24
import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition;
25 25
import com.sap.sailing.domain.leaderboard.Leaderboard;
26 26
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
27
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
27 28
import com.sap.sailing.domain.tracking.TrackedRace;
28
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
29 29
import com.sap.sailing.geocoding.ReverseGeocoder;
30 30
import com.sap.sse.common.TimePoint;
31 31
import com.sap.sse.common.Util;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoreCorrection.java
... ...
@@ -4,6 +4,7 @@ import java.io.Serializable;
4 4
import java.util.concurrent.Callable;
5 5
6 6
import com.sap.sailing.domain.base.Competitor;
7
+import com.sap.sailing.domain.base.Fleet;
7 8
import com.sap.sailing.domain.base.RaceColumn;
8 9
import com.sap.sailing.domain.common.MaxPointsReason;
9 10
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
... ...
@@ -121,8 +122,9 @@ public interface ScoreCorrection extends Serializable {
121 122
* for any competitor who in <code>raceInLeaderboard</code> is not in a tracked race and hence the fleet assignment
122 123
* cannot be determined. This is helpful, e.g., for progress detection. If score corrections are present for such
123 124
* untracked competitors then all untracked fleets need to be assumed as finished.
125
+ * @param fleet TODO
124 126
*/
125
- boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard);
127
+ boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard, Fleet fleet);
126 128
127 129
/**
128 130
* @return all race columns for which this score corrections object has at least one correction; note that this
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java
... ...
@@ -290,7 +290,6 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
290 290
}
291 291
}
292 292
}
293
- // TODO bug5877: pass leaderboard and competitors and a totalPointsSupplier (based on totalPointsCache, see call to compareByBetterScore below) to allow for identifying competitors promoted through to later medal race
294 293
int result = scoringScheme.compareByMedalRaceParticipation(zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, zeroBasedIndexOfLastMedalSeriesInWhichO2Scored);
295 294
if (result == 0) {
296 295
result = defaultFleetBasedComparisonResult;
... ...
@@ -482,29 +481,26 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
482 481
}
483 482
484 483
/**
485
- * If the race column only has one fleet, no decision is made and 0 is returned. Otherwise, if <code>fleet</code> is the
486
- * best fleet with others in the column being worse, return "better" (lesser; -1). If <code>fleet</code> is the worst fleet
487
- * with others in the column being better, return "worse" (greater; 1). Otherwise, return 0.
484
+ * If the race column only has one fleet, no decision is made and 0 is returned. Otherwise, if there are other fleets with
485
+ * a {@link Fleet#getOrdering() rank} different from that of <code>fleet</code>, we want to sort competitors with no fleet
486
+ * assignment to the "worse" end of the leaderboard, therefore returning 1. Otherwise, 0 is returned.
488 487
*/
489 488
private int extremeFleetComparison(RaceColumn raceColumn, Fleet fleet) {
490
- boolean allOthersAreGreater = true;
491
- boolean allOthersAreLess = true;
489
+ boolean greaterFleetExists = false;
490
+ boolean lesserFleetExists = false;
492 491
boolean othersExist = false;
493 492
for (Fleet f : raceColumn.getFleets()) {
494 493
if (f != fleet) {
495 494
othersExist = true;
496
- allOthersAreGreater = allOthersAreGreater && f.compareTo(fleet) > 0;
497
- allOthersAreLess = allOthersAreLess && f.compareTo(fleet) < 0;
495
+ greaterFleetExists = greaterFleetExists || f.compareTo(fleet) > 0;
496
+ lesserFleetExists = lesserFleetExists || f.compareTo(fleet) < 0;
498 497
}
499 498
}
500
- int result = 0;
501
- if (othersExist) {
502
- assert !(allOthersAreGreater && allOthersAreLess);
503
- if (allOthersAreGreater) {
504
- result = -1;
505
- } else if (allOthersAreLess) {
506
- result = 1;
507
- }
499
+ final int result;
500
+ if (othersExist && (greaterFleetExists || lesserFleetExists)) {
501
+ result = -1; // the competitor with no fleet is considered worse than a competitor in one of the ranked fleets
502
+ } else {
503
+ result = 0;
508 504
}
509 505
return result;
510 506
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java
... ...
@@ -14,6 +14,7 @@ import java.util.logging.Logger;
14 14
15 15
import com.sap.sailing.domain.base.Competitor;
16 16
import com.sap.sailing.domain.base.Course;
17
+import com.sap.sailing.domain.base.Fleet;
17 18
import com.sap.sailing.domain.base.RaceColumn;
18 19
import com.sap.sailing.domain.base.RaceColumnInSeries;
19 20
import com.sap.sailing.domain.common.MaxPointsReason;
... ...
@@ -610,19 +611,17 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
610 611
611 612
@Override
612 613
public boolean hasCorrectionFor(RaceColumn raceInLeaderboard) {
613
- return internalHasScoreCorrectionFor(raceInLeaderboard, /* considerOnlyUntrackedRaces */ false);
614
+ return internalHasScoreCorrectionFor(raceInLeaderboard);
614 615
}
615 616
616
- private boolean internalHasScoreCorrectionFor(RaceColumn raceInLeaderboard, boolean considerOnlyUntrackedRaces) {
617
- for (Pair<Competitor, RaceColumn> correctedScoresKey : correctedScores.keySet()) {
618
- if (correctedScoresKey.getB() == raceInLeaderboard &&
619
- (!considerOnlyUntrackedRaces || raceInLeaderboard.getTrackedRace(correctedScoresKey.getA()) == null)) {
617
+ private boolean internalHasScoreCorrectionFor(RaceColumn raceInLeaderboard) {
618
+ for (final Pair<Competitor, RaceColumn> correctedScoresKey : correctedScores.keySet()) {
619
+ if (correctedScoresKey.getB() == raceInLeaderboard) {
620 620
return true;
621 621
}
622 622
}
623 623
for (Pair<Competitor, RaceColumn> maxPointsReasonsKey : maxPointsReasons.keySet()) {
624
- if (maxPointsReasonsKey.getB() == raceInLeaderboard &&
625
- (!considerOnlyUntrackedRaces || raceInLeaderboard.getTrackedRace(maxPointsReasonsKey.getA()) == null)) {
624
+ if (maxPointsReasonsKey.getB() == raceInLeaderboard) {
626 625
return true;
627 626
}
628 627
}
... ...
@@ -630,8 +629,21 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection {
630 629
}
631 630
632 631
@Override
633
- public boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard) {
634
- return internalHasScoreCorrectionFor(raceInLeaderboard, /* considerOnlyUntrackedRaces */ true);
632
+ public boolean hasCorrectionForNonTrackedFleet(RaceColumn raceInLeaderboard, Fleet fleet) {
633
+ boolean result;
634
+ if (raceInLeaderboard.getTrackedRace(fleet) == null) {
635
+ result = false;
636
+ for (final Competitor competitor : raceInLeaderboard.getAllCompetitors(fleet)) {
637
+ final Pair<Competitor, RaceColumn> key = raceInLeaderboard.getKey(competitor);
638
+ if (correctedScores.containsKey(key) || maxPointsReasons.containsKey(key)) {
639
+ result = true;
640
+ break;
641
+ }
642
+ }
643
+ } else {
644
+ result = false;
645
+ }
646
+ return result;
635 647
}
636 648
637 649
@Override
java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/IncrementalApproximatedFixesCalculatorImpl.java
... ...
@@ -10,10 +10,10 @@ import java.util.NavigableSet;
10 10
import com.sap.sailing.domain.base.Competitor;
11 11
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
12 12
import com.sap.sailing.domain.maneuverdetection.IncrementalApproximatedFixesCalculator;
13
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
13 14
import com.sap.sailing.domain.tracking.GPSFixTrack;
14 15
import com.sap.sailing.domain.tracking.MarkPassing;
15 16
import com.sap.sailing.domain.tracking.TrackedRace;
16
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
17 17
import com.sap.sse.common.Duration;
18 18
import com.sap.sse.common.TimePoint;
19 19
import com.sap.sse.common.Util;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java
... ...
@@ -30,9 +30,9 @@ import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl;
30 30
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprint;
31 31
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintFactory;
32 32
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
33
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
33 34
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
34 35
import com.sap.sailing.domain.tracking.MarkPassing;
35
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
36 36
import com.sap.sse.common.TimePoint;
37 37
import com.sap.sse.common.Util;
38 38
import com.sap.sse.common.Util.Pair;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java
... ...
@@ -10,7 +10,7 @@ import com.sap.sailing.domain.base.Mark;
10 10
import com.sap.sailing.domain.base.Waypoint;
11 11
import com.sap.sailing.domain.common.tracking.GPSFix;
12 12
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
13
-import com.sap.sailing.domain.tracking.AddResult;
13
+import com.sap.sailing.domain.shared.tracking.AddResult;
14 14
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
15 15
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
16 16
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java
... ...
@@ -29,13 +29,13 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
29 29
import com.sap.sailing.domain.markpassingcalculation.Candidate;
30 30
import com.sap.sailing.domain.markpassingcalculation.CandidateChooser;
31 31
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
32
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
32 33
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
33 34
import com.sap.sailing.domain.tracking.GPSFixTrack;
34 35
import com.sap.sailing.domain.tracking.MarkPassing;
35 36
import com.sap.sailing.domain.tracking.TrackedLeg;
36 37
import com.sap.sailing.domain.tracking.TrackedRace;
37 38
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
38
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
39 39
import com.sap.sse.common.Distance;
40 40
import com.sap.sse.common.Duration;
41 41
import com.sap.sse.common.Speed;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java
... ...
@@ -35,13 +35,13 @@ import com.sap.sailing.domain.common.tracking.GPSFix;
35 35
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
36 36
import com.sap.sailing.domain.markpassingcalculation.Candidate;
37 37
import com.sap.sailing.domain.markpassingcalculation.CandidateFinder;
38
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
38 39
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
39 40
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
40 41
import com.sap.sailing.domain.tracking.GPSFixTrack;
41 42
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
42 43
import com.sap.sailing.domain.tracking.TrackedRace;
43 44
import com.sap.sailing.domain.tracking.impl.MarkPositionAtTimePointCacheImpl;
44
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
45 45
import com.sap.sse.common.Bearing;
46 46
import com.sap.sse.common.Distance;
47 47
import com.sap.sse.common.Duration;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/WaypointPositionAndDistanceCache.java
... ...
@@ -21,7 +21,7 @@ import com.sap.sailing.domain.base.Mark;
21 21
import com.sap.sailing.domain.base.Waypoint;
22 22
import com.sap.sailing.domain.common.Position;
23 23
import com.sap.sailing.domain.common.tracking.GPSFix;
24
-import com.sap.sailing.domain.tracking.AddResult;
24
+import com.sap.sailing.domain.shared.tracking.AddResult;
25 25
import com.sap.sailing.domain.tracking.GPSFixTrack;
26 26
import com.sap.sailing.domain.tracking.TrackedRace;
27 27
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DummyTrackedRace.java
... ...
@@ -43,6 +43,8 @@ import com.sap.sailing.domain.leaderboard.impl.RankAndRankComparable;
43 43
import com.sap.sailing.domain.polars.PolarDataService;
44 44
import com.sap.sailing.domain.ranking.RankingMetric;
45 45
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
46
+import com.sap.sailing.domain.shared.tracking.LineDetails;
47
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
46 48
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
47 49
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
48 50
import com.sap.sailing.domain.windestimation.IncrementalWindEstimation;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicMappedTrack.java
... ...
@@ -1,5 +1,6 @@
1 1
package com.sap.sailing.domain.tracking;
2 2
3
+import com.sap.sailing.domain.shared.tracking.MappedTrack;
3 4
import com.sap.sse.common.Timed;
4 5
5 6
/**
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrack.java
... ...
@@ -1,5 +1,6 @@
1 1
package com.sap.sailing.domain.tracking;
2 2
3
+import com.sap.sailing.domain.shared.tracking.Track;
3 4
import com.sap.sse.common.Timed;
4 5
5 6
public interface DynamicTrack<FixType extends Timed> extends Track<FixType> {
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackWithRemove.java
... ...
@@ -26,5 +26,5 @@ public interface DynamicTrackWithRemove<FixType extends Timed> extends DynamicTr
26 26
*/
27 27
boolean remove(FixType fix);
28 28
29
- void removeAllUpToAndIncluding(FixType fix);
29
+ void removeAllUpToExcluding(FixType fix);
30 30
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java
... ...
@@ -10,6 +10,7 @@ import com.sap.sailing.domain.common.confidence.Weigher;
10 10
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
11 11
import com.sap.sailing.domain.common.tracking.GPSFix;
12 12
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
13
+import com.sap.sailing.domain.shared.tracking.MappedTrack;
13 14
import com.sap.sse.common.Distance;
14 15
import com.sap.sse.common.Duration;
15 16
import com.sap.sse.common.Speed;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSTrackListener.java
... ...
@@ -1,6 +1,7 @@
1 1
package com.sap.sailing.domain.tracking;
2 2
3 3
import com.sap.sailing.domain.common.tracking.GPSFix;
4
+import com.sap.sailing.domain.shared.tracking.AddResult;
4 5
5 6
public interface GPSTrackListener<ItemType, FixType extends GPSFix> extends TrackListener {
6 7
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java
... ...
@@ -17,6 +17,7 @@ import com.sap.sailing.domain.common.WindSource;
17 17
import com.sap.sailing.domain.common.tracking.GPSFix;
18 18
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
19 19
import com.sap.sailing.domain.common.tracking.SensorFix;
20
+import com.sap.sailing.domain.shared.tracking.AddResult;
20 21
import com.sap.sse.common.TimePoint;
21 22
22 23
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackingHandler.java
... ...
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
21 21
import com.sap.sailing.domain.maneuverhash.ManeuverRaceFingerprintRegistry;
22 22
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
23 23
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
24
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
24 25
import com.sap.sse.common.Color;
25 26
import com.sap.sse.common.Duration;
26 27
import com.sap.sse.util.ThreadLocalTransporter;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/SensorFixTrack.java
... ...
@@ -3,6 +3,8 @@ package com.sap.sailing.domain.tracking;
3 3
import java.io.Serializable;
4 4
5 5
import com.sap.sailing.domain.common.tracking.SensorFix;
6
+import com.sap.sailing.domain.shared.tracking.MappedTrack;
7
+import com.sap.sailing.domain.shared.tracking.Track;
6 8
import com.sap.sse.common.WithID;
7 9
8 10
/**
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/SensorFixTrackListener.java
... ...
@@ -1,6 +1,7 @@
1 1
package com.sap.sailing.domain.tracking;
2 2
3 3
import com.sap.sailing.domain.common.tracking.SensorFix;
4
+import com.sap.sailing.domain.shared.tracking.AddResult;
4 5
5 6
/**
6 7
* Listener to observe a {@link com.sap.sailing.domain.tracking.SensorFixTrack} to get informed whenever a new fix is
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
... ...
@@ -69,6 +69,8 @@ import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
69 69
import com.sap.sailing.domain.racelog.tracking.SensorFixStore;
70 70
import com.sap.sailing.domain.ranking.RankingMetric;
71 71
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
72
+import com.sap.sailing.domain.shared.tracking.LineDetails;
73
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
72 74
import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache;
73 75
import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl;
74 76
import com.sap.sailing.domain.windestimation.IncrementalWindEstimation;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java
... ...
@@ -13,6 +13,7 @@ import com.sap.sailing.domain.common.NoWindException;
13 13
import com.sap.sailing.domain.maneuverhash.ManeuverRaceFingerprintRegistry;
14 14
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
15 15
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
16
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
16 17
import com.sap.sse.common.TimePoint;
17 18
import com.sap.sse.metering.HasCPUMeter;
18 19
import com.sap.sse.util.ThreadLocalTransporter;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java
... ...
@@ -12,7 +12,7 @@ import com.sap.sailing.domain.common.WindSource;
12 12
import com.sap.sailing.domain.common.tracking.GPSFix;
13 13
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
14 14
import com.sap.sailing.domain.common.tracking.SensorFix;
15
-import com.sap.sailing.domain.tracking.AddResult;
15
+import com.sap.sailing.domain.shared.tracking.AddResult;
16 16
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
17 17
import com.sap.sailing.domain.tracking.MarkPassing;
18 18
import com.sap.sailing.domain.tracking.RaceChangeListener;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackImpl.java
... ...
@@ -10,12 +10,13 @@ import com.sap.sailing.domain.common.scalablevalue.impl.ScalableDistance;
10 10
import com.sap.sailing.domain.common.tracking.BravoExtendedFix;
11 11
import com.sap.sailing.domain.common.tracking.BravoFix;
12 12
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
13
-import com.sap.sailing.domain.tracking.AddResult;
13
+import com.sap.sailing.domain.shared.tracking.AddResult;
14
+import com.sap.sailing.domain.shared.tracking.Track;
15
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
14 16
import com.sap.sailing.domain.tracking.BravoFixTrack;
15 17
import com.sap.sailing.domain.tracking.DynamicBravoFixTrack;
16 18
import com.sap.sailing.domain.tracking.GPSFixTrack;
17 19
import com.sap.sailing.domain.tracking.GPSTrackListener;
18
-import com.sap.sailing.domain.tracking.Track;
19 20
import com.sap.sailing.domain.tracking.TrackedRace;
20 21
import com.sap.sse.common.Bearing;
21 22
import com.sap.sse.common.Distance;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CourseChangeBasedTrackApproximation.java
... ...
@@ -17,7 +17,8 @@ import com.sap.sailing.domain.base.Competitor;
17 17
import com.sap.sailing.domain.common.SpeedWithBearing;
18 18
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
19 19
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
20
-import com.sap.sailing.domain.tracking.AddResult;
20
+import com.sap.sailing.domain.shared.tracking.AddResult;
21
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
21 22
import com.sap.sailing.domain.tracking.GPSFixTrack;
22 23
import com.sap.sailing.domain.tracking.GPSTrackListener;
23 24
import com.sap.sse.common.Duration;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java
... ...
@@ -19,11 +19,12 @@ import com.sap.sailing.domain.common.NoWindException;
19 19
import com.sap.sailing.domain.common.impl.MeterDistance;
20 20
import com.sap.sailing.domain.common.tracking.GPSFix;
21 21
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
22
-import com.sap.sailing.domain.tracking.AddResult;
22
+import com.sap.sailing.domain.shared.tracking.AddResult;
23
+import com.sap.sailing.domain.shared.tracking.Track;
24
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
23 25
import com.sap.sailing.domain.tracking.GPSFixTrack;
24 26
import com.sap.sailing.domain.tracking.MarkPassing;
25 27
import com.sap.sailing.domain.tracking.RaceChangeListener;
26
-import com.sap.sailing.domain.tracking.Track;
27 28
import com.sap.sailing.domain.tracking.TrackedLeg;
28 29
import com.sap.sailing.domain.tracking.TrackedRace;
29 30
import com.sap.sse.common.Distance;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicMappedTrackImpl.java
... ...
@@ -1,5 +1,6 @@
1 1
package com.sap.sailing.domain.tracking.impl;
2 2
3
+import com.sap.sailing.domain.shared.tracking.impl.MappedTrackImpl;
3 4
import com.sap.sailing.domain.tracking.DynamicMappedTrack;
4 5
import com.sap.sailing.domain.tracking.DynamicTrack;
5 6
import com.sap.sse.common.Timed;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackImpl.java
... ...
@@ -1,5 +1,6 @@
1 1
package com.sap.sailing.domain.tracking.impl;
2 2
3
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
3 4
import com.sap.sailing.domain.tracking.DynamicTrack;
4 5
import com.sap.sse.common.Timed;
5 6
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackWithRemoveImpl.java
... ...
@@ -27,7 +27,7 @@ public class DynamicTrackWithRemoveImpl<FixType extends Timed> extends DynamicTr
27 27
}
28 28
29 29
@Override
30
- public void removeAllUpToAndIncluding(FixType fix) {
31
- getInternalFixes().removeAllLessOrEqual(fix);
30
+ public void removeAllUpToExcluding(FixType fix) {
31
+ getInternalFixes().removeAllLessThan(fix);
32 32
}
33 33
}
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java
... ...
@@ -49,7 +49,8 @@ import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
49 49
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
50 50
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
51 51
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
52
-import com.sap.sailing.domain.tracking.AddResult;
52
+import com.sap.sailing.domain.shared.tracking.AddResult;
53
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
53 54
import com.sap.sailing.domain.tracking.CourseDesignChangedListener;
54 55
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
55 56
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
... ...
@@ -67,7 +68,6 @@ import com.sap.sailing.domain.tracking.TrackFactory;
67 68
import com.sap.sailing.domain.tracking.TrackedLeg;
68 69
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
69 70
import com.sap.sailing.domain.tracking.TrackedRegatta;
70
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
71 71
import com.sap.sailing.domain.tracking.TrackingDataLoader;
72 72
import com.sap.sailing.domain.tracking.WindStore;
73 73
import com.sap.sailing.domain.tracking.WindTrack;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java
... ...
@@ -33,12 +33,15 @@ import com.sap.sailing.domain.common.tracking.GPSFix;
33 33
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
34 34
import com.sap.sailing.domain.common.tracking.WithValidityCache;
35 35
import com.sap.sailing.domain.common.tracking.impl.CompactPositionHelper;
36
-import com.sap.sailing.domain.tracking.AddResult;
36
+import com.sap.sailing.domain.shared.tracking.AddResult;
37
+import com.sap.sailing.domain.shared.tracking.Track;
38
+import com.sap.sailing.domain.shared.tracking.impl.MappedTrackImpl;
39
+import com.sap.sailing.domain.shared.tracking.impl.PartialNavigableSetView;
40
+import com.sap.sailing.domain.shared.tracking.impl.TimeRangeCache;
37 41
import com.sap.sailing.domain.tracking.GPSFixTrack;
38 42
import com.sap.sailing.domain.tracking.GPSTrackListener;
39 43
import com.sap.sailing.domain.tracking.SpeedWithBearingStep;
40 44
import com.sap.sailing.domain.tracking.SpeedWithBearingStepsIterable;
41
-import com.sap.sailing.domain.tracking.Track;
42 45
import com.sap.sailing.domain.tracking.TrackedRace;
43 46
import com.sap.sse.common.Bearing;
44 47
import com.sap.sse.common.Distance;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java
... ...
@@ -9,7 +9,7 @@ import java.util.NavigableSet;
9 9
10 10
import com.sap.sailing.domain.common.tracking.GPSFix;
11 11
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
12
-import com.sap.sailing.domain.tracking.AddResult;
12
+import com.sap.sailing.domain.shared.tracking.AddResult;
13 13
import com.sap.sailing.domain.tracking.GPSFixTrack;
14 14
import com.sap.sailing.domain.tracking.GPSTrackListener;
15 15
import com.sap.sse.common.Speed;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/SensorFixTrackImpl.java
... ...
@@ -4,7 +4,7 @@ import java.io.Serializable;
4 4
import java.util.function.Consumer;
5 5
6 6
import com.sap.sailing.domain.common.tracking.SensorFix;
7
-import com.sap.sailing.domain.tracking.AddResult;
7
+import com.sap.sailing.domain.shared.tracking.AddResult;
8 8
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
9 9
import com.sap.sailing.domain.tracking.SensorFixTrack;
10 10
import com.sap.sailing.domain.tracking.SensorFixTrackListener;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java
... ...
@@ -27,7 +27,8 @@ import com.sap.sailing.domain.common.impl.WindImpl;
27 27
import com.sap.sailing.domain.common.tracking.GPSFix;
28 28
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
29 29
import com.sap.sailing.domain.common.tracking.SensorFix;
30
-import com.sap.sailing.domain.tracking.AddResult;
30
+import com.sap.sailing.domain.shared.tracking.AddResult;
31
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
31 32
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
32 33
import com.sap.sailing.domain.tracking.MarkPassing;
33 34
import com.sap.sailing.domain.tracking.TrackedRace;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
... ...
@@ -144,13 +144,17 @@ import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
144 144
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
145 145
import com.sap.sailing.domain.ranking.RankingMetric;
146 146
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
147
+import com.sap.sailing.domain.shared.tracking.AddResult;
148
+import com.sap.sailing.domain.shared.tracking.LineDetails;
149
+import com.sap.sailing.domain.shared.tracking.Track;
150
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
151
+import com.sap.sailing.domain.shared.tracking.impl.LineDetailsImpl;
152
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
147 153
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
148
-import com.sap.sailing.domain.tracking.AddResult;
149 154
import com.sap.sailing.domain.tracking.BravoFixTrack;
150 155
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
151 156
import com.sap.sailing.domain.tracking.GPSFixTrack;
152 157
import com.sap.sailing.domain.tracking.GPSTrackListener;
153
-import com.sap.sailing.domain.tracking.LineDetails;
154 158
import com.sap.sailing.domain.tracking.Maneuver;
155 159
import com.sap.sailing.domain.tracking.MarkPassing;
156 160
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
... ...
@@ -158,7 +162,6 @@ import com.sap.sailing.domain.tracking.RaceChangeListener;
158 162
import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
159 163
import com.sap.sailing.domain.tracking.RaceListener;
160 164
import com.sap.sailing.domain.tracking.SensorFixTrack;
161
-import com.sap.sailing.domain.tracking.Track;
162 165
import com.sap.sailing.domain.tracking.TrackFactory;
163 166
import com.sap.sailing.domain.tracking.TrackedLeg;
164 167
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
... ...
@@ -166,7 +169,6 @@ import com.sap.sailing.domain.tracking.TrackedRace;
166 169
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
167 170
import com.sap.sailing.domain.tracking.TrackedRaceWithWindEssentials;
168 171
import com.sap.sailing.domain.tracking.TrackedRegatta;
169
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
170 172
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
171 173
import com.sap.sailing.domain.tracking.WindPositionMode;
172 174
import com.sap.sailing.domain.tracking.WindStore;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java
... ...
@@ -25,12 +25,12 @@ import com.sap.sailing.domain.common.NoWindException;
25 25
import com.sap.sailing.domain.maneuverhash.ManeuverRaceFingerprintRegistry;
26 26
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
27 27
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
28
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
28 29
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
29 30
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
30 31
import com.sap.sailing.domain.tracking.RaceListener;
31 32
import com.sap.sailing.domain.tracking.TrackedRace;
32 33
import com.sap.sailing.domain.tracking.TrackedRegatta;
33
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
34 34
import com.sap.sailing.domain.tracking.WindStore;
35 35
import com.sap.sse.common.TimePoint;
36 36
import com.sap.sse.common.Util;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/VirtualWindTrackImpl.java
... ...
@@ -4,6 +4,7 @@ import java.util.NavigableSet;
4 4
5 5
import com.sap.sailing.domain.common.Position;
6 6
import com.sap.sailing.domain.common.Wind;
7
+import com.sap.sailing.domain.shared.tracking.impl.PartialNavigableSetView;
7 8
import com.sap.sailing.domain.tracking.TrackedRace;
8 9
import com.sap.sailing.domain.tracking.WindWithConfidence;
9 10
import com.sap.sse.common.TimePoint;
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java
... ...
@@ -23,6 +23,7 @@ import com.sap.sailing.domain.common.tracking.impl.PreciseCompactWindImpl;
23 23
import com.sap.sailing.domain.common.tracking.impl.VeryCompactWindImpl;
24 24
import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager;
25 25
import com.sap.sailing.domain.confidence.ConfidenceFactory;
26
+import com.sap.sailing.domain.shared.tracking.impl.TrackImpl;
26 27
import com.sap.sailing.domain.tracking.WindListener;
27 28
import com.sap.sailing.domain.tracking.WindTrack;
28 29
import com.sap.sailing.domain.tracking.WindWithConfidence;
java/com.sap.sailing.geocoding/src/com/sap/sailing/geocoding/impl/ReverseGeocoderImpl.java
... ...
@@ -14,6 +14,7 @@ import java.util.Collections;
14 14
import java.util.Comparator;
15 15
import java.util.Iterator;
16 16
import java.util.List;
17
+import java.util.Optional;
17 18
import java.util.Random;
18 19
import java.util.TimeZone;
19 20
import java.util.logging.Level;
... ...
@@ -36,7 +37,21 @@ import com.sap.sse.common.Util;
36 37
import com.sap.sse.util.HttpUrlConnectionHelper;
37 38
38 39
public class ReverseGeocoderImpl implements ReverseGeocoder {
40
+ /**
41
+ * The system property name to get the comma separated list of geonames.org usernames; takes precedence over
42
+ * the environment variable whose name is given by {@link #USERNAMES_ENV_VARIABLE_NAME}.
43
+ */
39 44
private static final String USERNAMES_SYSTEM_PROPERTY_NAME = "geonames.org.usernames";
45
+
46
+ /**
47
+ * The environment variable name to get the comma separated list of geonames.org usernames. Not evaluated
48
+ * if the system property whose name is given by {@link #USERNAMES_SYSTEM_PROPERTY_NAME} is set.
49
+ */
50
+ private static final String USERNAMES_ENV_VARIABLE_NAME = "GEONAMES_ORG_USERNAMES";
51
+
52
+ /**
53
+ * Default username in case neither system property nor environment variable is set.
54
+ */
40 55
private static final String GEONAMES_DEFAULT_USER_NAME = "sailtracking0";
41 56
42 57
private static final String DATES = "dates";
... ...
@@ -63,7 +78,8 @@ public class ReverseGeocoderImpl implements ReverseGeocoder {
63 78
private static final Logger logger = Logger.getLogger(ReverseGeocoderImpl.class.getName());
64 79
65 80
public ReverseGeocoderImpl() {
66
- final String commaSeparatedUsernames = System.getProperty(USERNAMES_SYSTEM_PROPERTY_NAME, GEONAMES_DEFAULT_USER_NAME);
81
+ final String commaSeparatedUsernames = System.getProperty(USERNAMES_SYSTEM_PROPERTY_NAME,
82
+ Optional.ofNullable(System.getenv(USERNAMES_ENV_VARIABLE_NAME)).orElse(GEONAMES_DEFAULT_USER_NAME));
67 83
this.usernames = commaSeparatedUsernames.split(",");
68 84
}
69 85
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java
... ...
@@ -46,6 +46,7 @@ public class BoatClassImageResolver {
46 46
boatClassIconsMap.put(BoatClassMasterdata.BAVARIA_CRUISER_46.getDisplayName(), imageResources.BavariaCruiser46Icon());
47 47
boatClassIconsMap.put(BoatClassMasterdata.BB10M.getDisplayName(), imageResources.BB10MIcon());
48 48
boatClassIconsMap.put(BoatClassMasterdata.BENETEAU_FIRST_35.getDisplayName(), imageResources.BeneteauFirst35Icon());
49
+ boatClassIconsMap.put(BoatClassMasterdata.BENETEAU_FIRST_36.getDisplayName(), imageResources.BeneteauFirst36Icon());
49 50
boatClassIconsMap.put(BoatClassMasterdata.BENETEAU_FIRST_45.getDisplayName(), imageResources.BeneteauFirst45Icon());
50 51
boatClassIconsMap.put(BoatClassMasterdata.BRASSFAHRT_I.getDisplayName(), imageResources.Brassfahrt1Icon());
51 52
boatClassIconsMap.put(BoatClassMasterdata.BRASSFAHRT_II.getDisplayName(), imageResources.Brassfahrt2Icon());
... ...
@@ -63,6 +64,7 @@ public class BoatClassImageResolver {
63 64
boatClassIconsMap.put(BoatClassMasterdata.DRAGON_INT.getDisplayName(), imageResources.DragonIcon());
64 65
boatClassIconsMap.put(BoatClassMasterdata.DYAS.getDisplayName(), imageResources.DyasIcon());
65 66
boatClassIconsMap.put(BoatClassMasterdata.ELAN350.getDisplayName(), imageResources.Elan350Icon());
67
+ boatClassIconsMap.put(BoatClassMasterdata.ELAN_E4.getDisplayName(), imageResources.ElanE4Icon());
66 68
boatClassIconsMap.put(BoatClassMasterdata.ELLIOTT_6M.getDisplayName(), imageResources.Elliott6mIcon());
67 69
boatClassIconsMap.put(BoatClassMasterdata.EUROPE_INT.getDisplayName(), imageResources.EuropeIcon());
68 70
boatClassIconsMap.put(BoatClassMasterdata.EXTREME_40.getDisplayName(), imageResources.Extreme40Icon());
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java
... ...
@@ -96,6 +96,10 @@ public interface BoatClassImageResources extends ClientBundle {
96 96
@ImageOptions(preventInlining = true)
97 97
ImageResource BeneteauFirst35Icon();
98 98
99
+ @Source("com/sap/sailing/gwt/ui/client/images/boatclass/BENETEAU_FIRST_36.png")
100
+ @ImageOptions(preventInlining = true)
101
+ ImageResource BeneteauFirst36Icon();
102
+
99 103
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/BENETEAU_FIRST_45.png")
100 104
@ImageOptions(preventInlining = true)
101 105
ImageResource BeneteauFirst45Icon();
... ...
@@ -152,6 +156,10 @@ public interface BoatClassImageResources extends ClientBundle {
152 156
@ImageOptions(preventInlining = true)
153 157
ImageResource Elan350Icon();
154 158
159
+ @Source("com/sap/sailing/gwt/ui/client/images/boatclass/ELAN_E4.png")
160
+ @ImageOptions(preventInlining = true)
161
+ ImageResource ElanE4Icon();
162
+
155 163
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/EUROPE_INT.png")
156 164
@ImageOptions(preventInlining = true)
157 165
ImageResource EuropeIcon();
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
... ...
@@ -5,6 +5,19 @@
5 5
<div id="mainContent">
6 6
<h4 class="articleHeadline">What's New - SAP Sailing Analytics</h4>
7 7
<div class="innerContent">
8
+ <h5 class="articleSubheadline">October 2025</h5>
9
+ <ul class="bulletList">
10
+ <li>Bug fix: the "Edit Mark Passings" panel showed only up to 15 waypoints.</li>
11
+ <li>Competitors not assigned to any fleet will no longer be sorted in between a Gold
12
+ and a Silver fleet but will end up "at the bottom" of the leaderboard.</li>
13
+ <li>Bug fix: when a split-fleet series had results only entered manually as score
14
+ corrections, the progress bars for the series were not shown properly. All
15
+ fleets would be considered finished although some may not have seen results.</li>
16
+ </ul>
17
+ <h5 class="articleSubheadline">September 2025</h5>
18
+ <ul class="bulletList">
19
+ <li>Added CSV export for polar data histograms in Data Mining.</li>
20
+ </ul>
8 21
<h5 class="articleSubheadline">June 2025</h5>
9 22
<ul class="bulletList">
10 23
<li>Bug fix: the refresh interval in the Races/Tracking list ran into an overflow when races
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java
... ...
@@ -585,7 +585,8 @@ public class RaceContext {
585 585
}
586 586
}
587 587
ScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
588
- if (trackedRace == null && scoreCorrection != null && scoreCorrection.hasCorrectionForNonTrackedFleet(raceColumn)) {
588
+ // bug6168: for split fleets we need to check the fleet too
589
+ if (trackedRace == null && scoreCorrection != null && scoreCorrection.hasCorrectionForNonTrackedFleet(raceColumn, fleet)) {
589 590
return RaceViewState.FINISHED;
590 591
}
591 592
if (startTime != null) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java
... ...
@@ -341,6 +341,7 @@ public class WindPanel extends FormPanel implements FilterablePanelProvider<Race
341 341
contentPanel.add(new Label(stringMessages.seeIgtimiTabForAccountSettings()));
342 342
final CheckBox correctByDeclination = new CheckBox(stringMessages.declinationCheckbox());
343 343
correctByDeclination.setValue(true); // by default this is desirable because the Igtimi connector reads uncorrected magnetic values
344
+ final TextBox optionalBearerTokenBox = new TextBox();
344 345
final Button importButton = new Button(stringMessages.importWindFromIgtimi());
345 346
importButton.ensureDebugId("ImportWindFromIgtimi");
346 347
final HTML resultReport = new HTML();
... ...
@@ -357,7 +358,9 @@ public class WindPanel extends FormPanel implements FilterablePanelProvider<Race
357 358
if (Window.confirm(warningMessage)) {
358 359
resultReport.setText(stringMessages.loading());
359 360
sailingServiceWrite.importWindFromIgtimi(new ArrayList<>(refreshableRaceSelectionModel.getSelectedSet()),
360
- correctByDeclination.getValue(), new AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>>() {
361
+ correctByDeclination.getValue(),
362
+ Util.hasLength(optionalBearerTokenBox.getValue()) ? optionalBearerTokenBox.getValue().trim() : null,
363
+ new AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>>() {
361 364
@Override
362 365
public void onFailure(Throwable caught) {
363 366
errorReporter.reportError(stringMessages.errorImportingIgtimiWind(caught.getMessage()));
... ...
@@ -383,6 +386,11 @@ public class WindPanel extends FormPanel implements FilterablePanelProvider<Race
383 386
}
384 387
});
385 388
contentPanel.add(correctByDeclination);
389
+ final HorizontalPanel tokenPanel = new HorizontalPanel();
390
+ tokenPanel.setSpacing(5);
391
+ tokenPanel.add(new Label(stringMessages.optionalBearerTokenForWindImport()));
392
+ tokenPanel.add(optionalBearerTokenBox);
393
+ contentPanel.add(tokenPanel);
386 394
contentPanel.add(importButton);
387 395
contentPanel.add(resultReport);
388 396
return igtimiWindImportRootPanel;
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceWrite.java
... ...
@@ -243,7 +243,7 @@ public interface SailingServiceWrite extends FileStorageManagementGwtService, Sa
243 243
throws NotFoundException, NotDenotableForRaceLogTrackingException;
244 244
245 245
Map<RegattaAndRaceIdentifier, Integer> importWindFromIgtimi(List<RaceDTO> selectedRaces,
246
- boolean correctByDeclination)
246
+ boolean correctByDeclination, String optionalBearerTokenOrNull)
247 247
throws IllegalStateException, Exception;
248 248
249 249
IgtimiDataAccessWindowWithSecurityDTO addIgtimiDataAccessWindow(String deviceSerialNumber, Date from, Date to);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceWriteAsync.java
... ...
@@ -586,7 +586,7 @@ public interface SailingServiceWriteAsync extends FileStorageManagementGwtServic
586 586
void removeIgtimiDevice(String deviceSerialNumber, AsyncCallback<Void> asyncCallback);
587 587
588 588
void importWindFromIgtimi(List<RaceDTO> selectedRaces, boolean correctByDeclination,
589
- AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>> asyncCallback);
589
+ String optionalBearerTokenOrNull, AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>> asyncCallback);
590 590
591 591
/**
592 592
* The boolean result reflects whether a connection to the device identified by {@code serialNumber}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
... ...
@@ -2561,4 +2561,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
2561 2561
String successfullyCopiedPairings();
2562 2562
String selectFromRaceColumn();
2563 2563
String selectToRaceColumn();
2564
+ String exportTWAHistogramToCsv();
2565
+ String exportWindSpeedHistogramToCsv();
2566
+ String optionalBearerTokenForWindImport();
2564 2567
}
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
... ...
@@ -2598,4 +2598,7 @@ selectRaceColumnsWhosePairingsToCopy=Race columns whose pairings to copy
2598 2598
errorCopyingPairings=Error copying pairings: {0}
2599 2599
successfullyCopiedPairings=Successfully copied pairings
2600 2600
selectFromRaceColumn=Select race column from where to start copying pairings
2601
-selectToRaceColumn=Select race colunm to where to copy pairings
... ...
\ No newline at end of file
0
+selectToRaceColumn=Select race colunm to where to copy pairings
1
+exportTWAHistogramToCsv=Export True Wind Angle histogram to CSV
2
+exportWindSpeedHistogramToCsv=Export Wind Speed histogram to CSV
3
+optionalBearerTokenForWindImport=Optional bearer token for wind import
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
... ...
@@ -2592,4 +2592,7 @@ selectRaceColumnsWhosePairingsToCopy=Rennspalten auswählen, deren Zuordnungen k
2592 2592
errorCopyingPairings=Fehler beim Kopieren der Zuordnungen: {0}
2593 2593
successfullyCopiedPairings=Zuordnungen erfolgreich kopiert
2594 2594
selectFromRaceColumn=Spalte auswählen, ab der die Zuordnungen kopiert werden sollen
2595
-selectToRaceColumn=Spalte auswählen, bis zu der die Zuordnungen kopiert werden sollen
... ...
\ No newline at end of file
0
+selectToRaceColumn=Spalte auswählen, bis zu der die Zuordnungen kopiert werden sollen
1
+exportTWAHistogramToCsv=Histogramm der wahren Windwinkel in CSV exportieren
2
+exportWindSpeedHistogramToCsv=Histogramm der wahren Windgeschwindigkeiten in CSV exportieren
3
+optionalBearerTokenForWindImport=Optionales Bearer Token für Wind-Import
... ...
\ No newline at end of file
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java
... ...
@@ -130,7 +130,7 @@ public class EditMarkPassingsPanel extends AbstractCompositeComponent<AbstractSe
130 130
enableButtons();
131 131
}
132 132
});
133
- wayPointSelectionTable = new BaseCelltable<Util.Pair<Integer, Date>>();
133
+ wayPointSelectionTable = new BaseCelltable<Util.Pair<Integer, Date>>(/* pageSize */10000);
134 134
wayPointSelectionTable.addColumn(new Column<Util.Pair<Integer, Date>, SafeHtml>(new AnchorCell()) {
135 135
@Override
136 136
public SafeHtml getValue(final Util.Pair<Integer, Date> object) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatClassVectorGraphicsResolver.java
... ...
@@ -46,15 +46,15 @@ public class BoatClassVectorGraphicsResolver {
46 46
BoatClassMasterdata.HOBIE_WILD_CAT, BoatClassMasterdata.HOBIE_16, BoatClassMasterdata.HOBIE_TIGER,
47 47
BoatClassMasterdata.A_CAT, BoatClassMasterdata.TORNADO, BoatClassMasterdata.FLYING_PHANTOM, BoatClassMasterdata.ORC_MULTIHULL);
48 48
BoatClassVectorGraphics keelBoatWithGennaker = new KeelBoatWithGennakerVectorGraphics(BoatClassMasterdata.ELAN350,
49
- BoatClassMasterdata.FIRST_CLASS_7_5,
49
+ BoatClassMasterdata.ELAN_E4, BoatClassMasterdata.FIRST_CLASS_7_5,
50 50
BoatClassMasterdata.J70, BoatClassMasterdata.J80, BoatClassMasterdata.J92, BoatClassMasterdata.J92S,
51 51
BoatClassMasterdata.B_ONE, BoatClassMasterdata.IRC, BoatClassMasterdata.LASER_SB3, BoatClassMasterdata.LONGTZE,
52 52
BoatClassMasterdata.RS_FEVA, BoatClassMasterdata.RS_TERA, BoatClassMasterdata.RS_VENTURE,
53 53
BoatClassMasterdata.RS_VAREO,
54 54
BoatClassMasterdata.RS100, BoatClassMasterdata.RS21, BoatClassMasterdata.TP52,
55 55
BoatClassMasterdata.CLUB_SWAN_50, BoatClassMasterdata.BAVARIA_CRUISER_41S, BoatClassMasterdata.BAVARIA_CRUISER_45,
56
- BoatClassMasterdata.BAVARIA_CRUISER_46, BoatClassMasterdata.BENETEAU_FIRST_35, BoatClassMasterdata.BENETEAU_FIRST_45,
57
- BoatClassMasterdata.SPAEKHUGGER, BoatClassMasterdata.SCAN_KAP_99,
56
+ BoatClassMasterdata.BAVARIA_CRUISER_46, BoatClassMasterdata.BENETEAU_FIRST_35, BoatClassMasterdata.BENETEAU_FIRST_36,
57
+ BoatClassMasterdata.BENETEAU_FIRST_45, BoatClassMasterdata.SPAEKHUGGER, BoatClassMasterdata.SCAN_KAP_99,
58 58
BoatClassMasterdata.BB10M, BoatClassMasterdata.WAYFARER, BoatClassMasterdata.X_332,
59 59
BoatClassMasterdata.BRASSFAHRT_I, BoatClassMasterdata.BRASSFAHRT_II, BoatClassMasterdata.BRASSFAHRT_III,
60 60
BoatClassMasterdata.BRASSFAHRT_IV, BoatClassMasterdata.BRASSFAHRT_V,
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarBackendResultsPresenter.java
... ...
@@ -59,7 +59,6 @@ public class PolarBackendResultsPresenter extends AbstractSailingResultsPresente
59 59
public PolarBackendResultsPresenter(Component<?> parent, ComponentContext<?> context,
60 60
StringMessages stringMessages) {
61 61
super(parent, context, stringMessages);
62
-
63 62
polarChart = ChartFactory.createPolarChart();
64 63
polarChart.getYAxis().setMin(0);
65 64
polarChartWrapperPanel = new SimpleLayoutPanel() {
... ...
@@ -70,7 +69,6 @@ public class PolarBackendResultsPresenter extends AbstractSailingResultsPresente
70 69
}
71 70
};
72 71
polarChartWrapperPanel.add(polarChart);
73
-
74 72
speedChart = ChartFactory.createSpeedChart(stringMessages);
75 73
angleChart = ChartFactory.createAngleChart(stringMessages);
76 74
speedAndAngleChart = new DockLayoutPanel(Unit.PCT) {
... ...
@@ -84,16 +82,12 @@ public class PolarBackendResultsPresenter extends AbstractSailingResultsPresente
84 82
};
85 83
speedAndAngleChart.addNorth(speedChart, 50);
86 84
speedAndAngleChart.addSouth(angleChart, 50);
87
-
88 85
dockLayoutPanel = new DockLayoutPanel(Unit.PCT);
89 86
dockLayoutPanel.addWest(polarChartWrapperPanel, 40);
90 87
dockLayoutPanel.addEast(speedAndAngleChart, 60);
91
-
92 88
ChartToCsvExporter chartToCsvExporter = new ChartToCsvExporter(stringMessages.csvCopiedToClipboard());
93
-
94 89
Button exportStatisticsCurveToCsvButton = new Button(stringMessages.exportStatisticsCurveToCsv(),
95 90
new ClickHandler() {
96
-
97 91
@Override
98 92
public void onClick(ClickEvent event) {
99 93
chartToCsvExporter.exportChartAsCsvToClipboard(polarChart);
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarResultsPresenter.java
... ...
@@ -22,7 +22,6 @@ import org.moxieapps.gwt.highcharts.client.plotOptions.SeriesPlotOptions;
22 22
23 23
import com.google.gwt.core.client.Scheduler;
24 24
import com.google.gwt.dom.client.Style.Unit;
25
-import com.google.gwt.event.dom.client.ClickEvent;
26 25
import com.google.gwt.event.dom.client.ClickHandler;
27 26
import com.google.gwt.user.client.ui.Button;
28 27
import com.google.gwt.user.client.ui.DockLayoutPanel;
... ...
@@ -104,15 +103,16 @@ public class PolarResultsPresenter extends AbstractSailingResultsPresenter<Setti
104 103
dockLayoutPanel = new DockLayoutPanel(Unit.PCT);
105 104
dockLayoutPanel.addWest(polarChartWrapperPanel, 40);
106 105
dockLayoutPanel.addEast(histogramChartsWrapperPanel, 60);
107
- ChartToCsvExporter chartToCsvExporter = new ChartToCsvExporter(stringMessages.csvCopiedToClipboard());
108
- Button exportStatisticsCurveToCsvButton = new Button(stringMessages.exportStatisticsCurveToCsv(),
109
- new ClickHandler() {
110
- @Override
111
- public void onClick(ClickEvent event) {
112
- chartToCsvExporter.exportChartAsCsvToClipboard(polarChart);
113
- }
114
- });
106
+ final ChartToCsvExporter chartToCsvExporter = new ChartToCsvExporter(stringMessages.csvCopiedToClipboard());
107
+ final Button exportStatisticsCurveToCsvButton = new Button(stringMessages.exportStatisticsCurveToCsv(),
108
+ (ClickHandler) e->chartToCsvExporter.exportChartAsCsvToClipboard(polarChart));
115 109
addControl(exportStatisticsCurveToCsvButton);
110
+ final Button exportTWAHistogramToCsvButton = new Button(stringMessages.exportTWAHistogramToCsv(),
111
+ (ClickHandler) e->chartToCsvExporter.exportChartAsCsvToClipboard(dataCountHistogramChart));
112
+ addControl(exportTWAHistogramToCsvButton);
113
+ final Button exportWindSpeedHistogramToCsvButton = new Button(stringMessages.exportWindSpeedHistogramToCsv(),
114
+ (ClickHandler) e->chartToCsvExporter.exportChartAsCsvToClipboard(dataCountPerAngleHistogramChart));
115
+ addControl(exportWindSpeedHistogramToCsvButton);
116 116
setSeriesShowAndHideHandler();
117 117
}
118 118
... ...
@@ -230,6 +230,7 @@ public class PolarResultsPresenter extends AbstractSailingResultsPresenter<Setti
230 230
if (point != null) {
231 231
Map<Double, Integer> histogramDataForAngle = histogramData.get(index);
232 232
Series dataCountPerAngleSeries = dataCountPerAngleHistogramChart.createSeries();
233
+ dataCountPerAngleSeries.setName(key.asString() + " - " + convertedAngle + stringMessages.degreesShort());
233 234
// Iterating over the histogram data without sorting the x coordinates ascending leads
234 235
// to a massive occurrence of Highcharts error 15, freezing the complete UI
235 236
List<Double> sortedAngles = new ArrayList<>(histogramDataForAngle.keySet());
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/Activator.java
... ...
@@ -1,5 +1,7 @@
1 1
package com.sap.sailing.gwt.ui.server;
2 2
3
+import java.util.Optional;
4
+
3 5
import org.osgi.framework.BundleActivator;
4 6
import org.osgi.framework.BundleContext;
5 7
... ...
@@ -10,11 +12,31 @@ public class Activator implements BundleActivator {
10 12
private SailingServiceImpl sailingServiceToStopWhenStopping;
11 13
private static Activator INSTANCE;
12 14
15
+ /**
16
+ * If the system property named after this constant is set, its value is used for Google Maps API authentication.
17
+ * It takes precedence over the environment variable named after {@link #GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_ENV_VAR_NAME}.
18
+ */
13 19
private final static String GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME = "google.maps.authenticationparams";
14 20
21
+ /**
22
+ * If the system property named after {@link #GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME} is not set,
23
+ * this environment variable is checked for Google Maps API authentication parameters.
24
+ */
25
+ private final static String GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_ENV_VAR_NAME = "GOOGLE_MAPS_AUTHENTICATION_PARAMS";
26
+
27
+ /**
28
+ * The system property named after this constant is expected to provide the YouTube V3 API key. Takes precedence over
29
+ * the environment variable named after {@link #YOUTUBE_V3_API_KEY_ENV_VAR_NAME}.
30
+ */
15 31
private final static String YOUTUBE_V3_API_KEY_PROPERTY_NAME = "youtube.api.key";
16 32
17 33
/**
34
+ * If the system property named after {@link #YOUTUBE_V3_API_KEY_PROPERTY_NAME} is not set, this environment variable
35
+ * is checked for the YouTube V3 API key.
36
+ */
37
+ private final static String YOUTUBE_V3_API_KEY_ENV_VAR_NAME = "YOUTUBE_V3_API_KEY";
38
+
39
+ /**
18 40
* Required by {@link GoogleMapsLoader#load(Runnable, String)} and to be provided through a system property named
19 41
* after {@link GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME}. The value would be something like
20 42
* {@code client=abcde&channel=fghij}.
... ...
@@ -34,8 +56,12 @@ public class Activator implements BundleActivator {
34 56
@Override
35 57
public void start(BundleContext context) throws Exception {
36 58
Activator.context = context;
37
- googleMapsLoaderAuthenticationParams = context.getProperty(GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME);
38
- youtubeApiKey = context.getProperty(YOUTUBE_V3_API_KEY_PROPERTY_NAME);
59
+ googleMapsLoaderAuthenticationParams = Optional
60
+ .ofNullable(context.getProperty(GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_PROPERTY_NAME))
61
+ .orElse(System.getenv(GOOGLE_MAPS_LOADER_AUTHENTICATION_PARAMS_ENV_VAR_NAME));
62
+ youtubeApiKey = Optional
63
+ .ofNullable(context.getProperty(YOUTUBE_V3_API_KEY_PROPERTY_NAME))
64
+ .orElse(System.getenv(YOUTUBE_V3_API_KEY_ENV_VAR_NAME));
39 65
}
40 66
41 67
@Override
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
... ...
@@ -292,6 +292,9 @@ import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
292 292
import com.sap.sailing.domain.regattalog.RegattaLogStore;
293 293
import com.sap.sailing.domain.resultimport.ResultUrlProvider;
294 294
import com.sap.sailing.domain.sharding.ShardingContext;
295
+import com.sap.sailing.domain.shared.tracking.LineDetails;
296
+import com.sap.sailing.domain.shared.tracking.Track;
297
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
295 298
import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter;
296 299
import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapterFactory;
297 300
import com.sap.sailing.domain.swisstimingadapter.SwissTimingArchiveConfiguration;
... ...
@@ -307,14 +310,11 @@ import com.sap.sailing.domain.tracking.BravoFixTrack;
307 310
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
308 311
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
309 312
import com.sap.sailing.domain.tracking.GPSFixTrack;
310
-import com.sap.sailing.domain.tracking.LineDetails;
311 313
import com.sap.sailing.domain.tracking.Maneuver;
312 314
import com.sap.sailing.domain.tracking.MarkPassing;
313
-import com.sap.sailing.domain.tracking.Track;
314 315
import com.sap.sailing.domain.tracking.TrackedLeg;
315 316
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
316 317
import com.sap.sailing.domain.tracking.TrackedRace;
317
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
318 318
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
319 319
import com.sap.sailing.domain.tracking.WindTrack;
320 320
import com.sap.sailing.domain.tracking.WindWithConfidence;
... ...
@@ -6099,7 +6099,17 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
6099 6099
return Activator.getInstance().getGoogleMapsLoaderAuthenticationParams();
6100 6100
}
6101 6101
6102
- protected IgtimiConnection createIgtimiConnection() {
6103
- return getIgtimiConnectionFactory().getOrCreateConnection(()->getSecurityService().getCurrentUser() != null ? getSecurityService().getAccessToken(getSecurityService().getCurrentUser().getName()) : null);
6102
+ /**
6103
+ * @param optionalBearerToken
6104
+ * if present, this bearer token is used to authenticate the Igtimi connection; otherwise, we try to use
6105
+ * Igtimi default credentials provided at startup through the {@code igtimi.bearer.token} system
6106
+ * property. Only if no such system property has been set, we look for a logged-in user in the
6107
+ * {@link #getSecurityService() security service} and use its access token
6108
+ */
6109
+ protected IgtimiConnection createIgtimiConnection(Optional<String> optionalBearerToken) {
6110
+ return optionalBearerToken.map(bearerToken->getIgtimiConnectionFactory().getOrCreateConnection(bearerToken))
6111
+ .orElse(getIgtimiConnectionFactory().getOrCreateConnection(()->getSecurityService().getCurrentUser() != null
6112
+ ? getSecurityService().getAccessToken(getSecurityService().getCurrentUser().getName())
6113
+ : null));
6104 6114
}
6105 6115
}
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceWriteImpl.java
... ...
@@ -2215,7 +2215,7 @@ public class SailingServiceWriteImpl extends SailingServiceImpl implements Saili
2215 2215
2216 2216
@Override
2217 2217
public Map<RegattaAndRaceIdentifier, Integer> importWindFromIgtimi(List<RaceDTO> selectedRaces,
2218
- boolean correctByDeclination)
2218
+ boolean correctByDeclination, String optionalBearerTokenOrNull)
2219 2219
throws IllegalStateException, ClientProtocolException, IOException, org.json.simple.parser.ParseException {
2220 2220
final List<DynamicTrackedRace> trackedRaces = new ArrayList<>();
2221 2221
if (selectedRaces != null && !selectedRaces.isEmpty()) {
... ...
@@ -2238,7 +2238,7 @@ public class SailingServiceWriteImpl extends SailingServiceImpl implements Saili
2238 2238
}
2239 2239
}
2240 2240
Map<RegattaAndRaceIdentifier, Integer> numberOfWindFixesImportedPerRace = new HashMap<RegattaAndRaceIdentifier, Integer>();
2241
- final IgtimiConnection conn = createIgtimiConnection();
2241
+ final IgtimiConnection conn = createIgtimiConnection(Optional.ofNullable(optionalBearerTokenOrNull));
2242 2242
// filter account based on used permissions to read account:
2243 2243
Map<TrackedRace, Integer> resultsForAccounts = conn.importWindIntoRace(trackedRaces, correctByDeclination);
2244 2244
for (Entry<TrackedRace, Integer> resultForAccount : resultsForAccounts.entrySet()) {
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/TrackingConnectorInfoDTO.java
... ...
@@ -2,7 +2,7 @@ package com.sap.sailing.gwt.ui.shared;
2 2
3 3
import com.google.gwt.core.shared.GwtIncompatible;
4 4
import com.google.gwt.user.client.rpc.IsSerializable;
5
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
5
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
6 6
7 7
public class TrackingConnectorInfoDTO implements IsSerializable {
8 8
private String trackingConnectorName;
java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/BENETEAU_FIRST_36.png
... ...
Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/BENETEAU_FIRST_36.png differ
java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ELAN_E4.png
... ...
Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/ELAN_E4.png differ
java/com.sap.sailing.ingestion/src/main/java/com/sap/sailing/ingestion/FixCombinationLambda.java
... ...
@@ -23,7 +23,7 @@ import com.amazonaws.services.lambda.runtime.Context;
23 23
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
24 24
import com.sap.sailing.domain.common.DeviceIdentifier;
25 25
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
26
-import com.sap.sailing.domain.tracking.impl.TimedComparator;
26
+import com.sap.sailing.domain.shared.tracking.impl.TimedComparator;
27 27
import com.sap.sailing.server.gateway.deserialization.impl.GPSFixMovingJsonDeserializer;
28 28
import com.sap.sailing.server.gateway.deserialization.impl.Helpers;
29 29
import com.sap.sailing.server.gateway.serialization.impl.GPSFixMovingJsonSerializer;
java/com.sap.sailing.nmeaconnector/META-INF/MANIFEST.MF
... ...
@@ -12,7 +12,8 @@ Require-Bundle: com.sap.sailing.domain,
12 12
com.sap.sse.common,
13 13
net.sf.marineapi,
14 14
com.sap.sailing.declination
15
-Import-Package: org.osgi.framework
15
+Import-Package: org.osgi.framework,
16
+ com.sap.sailing.domain.shared.tracking
16 17
Bundle-ActivationPolicy: lazy
17 18
Bundle-Activator: com.sap.sailing.nmeaconnector.impl.Activator
18 19
Automatic-Module-Name: com.sap.sailing.nmeaconnector
java/com.sap.sailing.nmeaconnector/src/com/sap/sailing/nmeaconnector/impl/KnotSpeedWithBearingAndTimepoint.java
... ...
@@ -1,13 +1,12 @@
1 1
package com.sap.sailing.nmeaconnector.impl;
2 2
3 3
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
4
-import com.sap.sailing.domain.tracking.Track;
5 4
import com.sap.sailing.nmeaconnector.TimedSpeedWithBearing;
6 5
import com.sap.sse.common.Bearing;
7 6
import com.sap.sse.common.TimePoint;
8 7
9 8
/**
10
- * Can be used as a wind vector in a {@link Track} because it has a {@link TimePoint}.
9
+ * Can be used as a wind vector in a {@code Track} because it has a {@link TimePoint}.
11 10
*
12 11
* @author Axel Uhl (d043530)
13 12
*
java/com.sap.sailing.polars.datamining.shared/src/com/sap/sailing/polars/datamining/shared/PolarAggregation.java
... ...
@@ -15,6 +15,10 @@ public interface PolarAggregation extends Serializable {
15 15
16 16
PolarDataMiningSettings getSettings();
17 17
18
+ /**
19
+ * Keys are the angles in degrees (0...359). The {@link Double} key of the inner map is the histogram x-value (e.g.
20
+ * 5.0 for wind speed 4.5...5.5 with step 1.0).
21
+ */
18 22
Map<Integer, Map<Double, Integer>> getCountHistogramPerAngle();
19 23
20 24
}
java/com.sap.sailing.polars.datamining.shared/src/com/sap/sailing/polars/datamining/shared/PolarAggregationImpl.java
... ...
@@ -11,8 +11,12 @@ public class PolarAggregationImpl implements PolarAggregation {
11 11
private double[] sumSpeedsPerAngle = new double[360];
12 12
private int[] countPerAngle = new int[360];
13 13
/**
14
- * FIXME Right now the histogram data is only valid, if the results are grouped by windrange.
15
- * Otherwise the column-indices of different ranges are mixed in one histogram.
14
+ * FIXME Right now the histogram data is only valid, if the results are grouped by windrange. Otherwise the
15
+ * column-indices of different ranges are mixed in one histogram.
16
+ * <p>
17
+ *
18
+ * Keys are the angles in degrees (0...359). The {@link Double} key of the inner map is the histogram x-value (e.g.
19
+ * 5.0 for wind speed 4.5...5.5 with step 1.0).
16 20
*/
17 21
private Map<Integer, Map<Double, Integer>> histogramData;
18 22
private int count = 0;
... ...
@@ -80,6 +84,4 @@ public class PolarAggregationImpl implements PolarAggregation {
80 84
public Map<Integer, Map<Double, Integer>> getCountHistogramPerAngle() {
81 85
return histogramData;
82 86
}
83
-
84
-
85 87
}
java/com.sap.sailing.polars.test/META-INF/MANIFEST.MF
... ...
@@ -6,6 +6,8 @@ Bundle-Version: 1.0.0.qualifier
6 6
Bundle-Vendor: SAP
7 7
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
8 8
Fragment-Host: com.sap.sailing.polars
9
+Import-Package: com.sap.sailing.domain.shared.tracking,
10
+ com.sap.sailing.domain.shared.tracking.impl
9 11
Require-Bundle: org.hamcrest;bundle-version="2.2.0",
10 12
org.mockito.mockito-core;bundle-version="1.10.14",
11 13
junit-jupiter-api;bundle-version="5.11.3",
java/com.sap.sailing.polars/META-INF/MANIFEST.MF
... ...
@@ -39,6 +39,7 @@ Require-Bundle:
39 39
org.apache.httpcomponents.httpcore;bundle-version="4.4.9",
40 40
com.sap.sailing.shared.server
41 41
Import-Package:
42
+ com.sap.sailing.domain.shared.tracking,
42 43
com.sap.sailing.util,
43 44
javax.ws.rs;version="1.1.1",
44 45
javax.ws.rs.core;version="1.1.1",
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/core/SeleniumTestInvocationProvider.java
... ...
@@ -2,6 +2,7 @@ package com.sap.sailing.selenium.core;
2 2
3 3
import java.util.Arrays;
4 4
import java.util.List;
5
+import java.util.Map.Entry;
5 6
import java.util.stream.Stream;
6 7
7 8
import org.junit.jupiter.api.extension.Extension;
... ...
@@ -33,6 +34,9 @@ public class SeleniumTestInvocationProvider implements TestTemplateInvocationCon
33 34
@Override
34 35
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
35 36
TestEnvironmentConfiguration config = TestEnvironmentConfiguration.getInstance();
37
+ for (Entry<String, String> e : config.getSystemProperties().entrySet()) {
38
+ System.setProperty(e.getKey(), e.getValue());
39
+ }
36 40
return config.getDriverDefinitions().stream().map(driverDef -> {
37 41
return new SeleniumTestContext(driverDef);
38 42
});
... ...
@@ -55,4 +59,4 @@ public class SeleniumTestInvocationProvider implements TestTemplateInvocationCon
55 59
return Arrays.asList(new SeleniumTestEnvironmentInjector(driverDefinition));
56 60
}
57 61
}
58
-}
... ...
\ No newline at end of file
0
+}
java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SimulatorOverlayTest.java
... ...
@@ -119,7 +119,7 @@ public class SimulatorOverlayTest extends AbstractSeleniumTest {
119 119
gpxInputStream.close();
120 120
final String routeconverterWindFileName = tmpFile.getAbsolutePath();
121 121
windPanel.importWindFromRouteconverter(routeconverterWindFileName, /* waiting up to 10 min */ 15 * 60);
122
- Thread.sleep(10000); // wait for 10s to allow all wind fixes to get processed by the PolarDataService
122
+ Thread.sleep(30000); // wait for 30s to allow all wind fixes to get processed by the PolarDataService
123 123
} finally {
124 124
tmpFile.delete();
125 125
}
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java
... ...
@@ -16,7 +16,7 @@ import com.sap.sailing.domain.base.EventBase;
16 16
import com.sap.sailing.domain.base.LeaderboardGroupBase;
17 17
import com.sap.sailing.domain.base.Venue;
18 18
import com.sap.sailing.domain.base.impl.StrippedEventImpl;
19
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
19
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
20 20
import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer;
21 21
import com.sap.sse.common.TimePoint;
22 22
import com.sap.sse.common.impl.MillisecondsTimePoint;
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/TrackingConnectorInfoJsonDeserializer.java
... ...
@@ -2,8 +2,8 @@ package com.sap.sailing.server.gateway.deserialization.impl;
2 2
3 3
import org.json.simple.JSONObject;
4 4
5
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
6
-import com.sap.sailing.domain.tracking.impl.TrackingConnectorInfoImpl;
5
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
6
+import com.sap.sailing.domain.shared.tracking.impl.TrackingConnectorInfoImpl;
7 7
import com.sap.sailing.server.gateway.serialization.impl.TrackingConnectorInfoJsonSerializer;
8 8
import com.sap.sse.shared.json.JsonDeserializationException;
9 9
import com.sap.sse.shared.json.JsonDeserializer;
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/coursedata/impl/CourseBaseWithGeometryJsonSerializer.java
... ...
@@ -8,7 +8,7 @@ import com.sap.sailing.domain.base.CourseBase;
8 8
import com.sap.sailing.domain.base.Leg;
9 9
import com.sap.sailing.domain.base.Waypoint;
10 10
import com.sap.sailing.domain.common.NauticalSide;
11
-import com.sap.sailing.domain.tracking.LineDetails;
11
+import com.sap.sailing.domain.shared.tracking.LineDetails;
12 12
import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseBaseWithGeometryJsonSerializer.CourseGeometry;
13 13
import com.sap.sailing.server.gateway.serialization.impl.PositionJsonSerializer;
14 14
import com.sap.sse.common.Bearing;
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java
... ...
@@ -10,7 +10,7 @@ import org.json.simple.JSONObject;
10 10
import com.sap.sailing.domain.base.EventBase;
11 11
import com.sap.sailing.domain.base.LeaderboardGroupBase;
12 12
import com.sap.sailing.domain.base.Venue;
13
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
13
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
14 14
import com.sap.sse.shared.json.JsonSerializer;
15 15
import com.sap.sse.shared.media.ImageDescriptor;
16 16
import com.sap.sse.shared.media.VideoDescriptor;
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/TrackingConnectorInfoJsonSerializer.java
... ...
@@ -2,7 +2,7 @@ package com.sap.sailing.server.gateway.serialization.impl;
2 2
3 3
import org.json.simple.JSONObject;
4 4
5
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
5
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
6 6
import com.sap.sse.shared.json.JsonSerializer;
7 7
8 8
public class TrackingConnectorInfoJsonSerializer implements JsonSerializer<TrackingConnectorInfo> {
java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java
... ...
@@ -115,9 +115,9 @@ import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent;
115 115
import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDIdentifierImpl;
116 116
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
117 117
import com.sap.sailing.domain.sharding.ShardingContext;
118
+import com.sap.sailing.domain.shared.tracking.LineDetails;
118 119
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
119 120
import com.sap.sailing.domain.tracking.GPSFixTrack;
120
-import com.sap.sailing.domain.tracking.LineDetails;
121 121
import com.sap.sailing.domain.tracking.Maneuver;
122 122
import com.sap.sailing.domain.tracking.MarkPassing;
123 123
import com.sap.sailing.domain.tracking.RaceWindCalculator;
java/com.sap.sailing.server.interface/src/com/sap/sailing/server/interfaces/RacingEventService.java
... ...
@@ -95,6 +95,7 @@ import com.sap.sailing.domain.racelog.tracking.SensorFixStoreSupplier;
95 95
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
96 96
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
97 97
import com.sap.sailing.domain.resultimport.ResultUrlProvider;
98
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
98 99
import com.sap.sailing.domain.statistics.Statistics;
99 100
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
100 101
import com.sap.sailing.domain.tracking.RaceHandle;
... ...
@@ -107,7 +108,6 @@ import com.sap.sailing.domain.tracking.TrackedRace;
107 108
import com.sap.sailing.domain.tracking.TrackedRegatta;
108 109
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
109 110
import com.sap.sailing.domain.tracking.TrackerManager;
110
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
111 111
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
112 112
import com.sap.sailing.domain.tracking.WindStore;
113 113
import com.sap.sailing.domain.tracking.WindTracker;
java/com.sap.sailing.server.interface/src/com/sap/sailing/server/operationaltransformation/CreateTrackedRace.java
... ...
@@ -4,10 +4,10 @@ import com.sap.sailing.domain.base.RaceDefinition;
4 4
import com.sap.sailing.domain.base.Regatta;
5 5
import com.sap.sailing.domain.common.RaceIdentifier;
6 6
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
7
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
7 8
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
8 9
import com.sap.sailing.domain.tracking.TrackedRace;
9 10
import com.sap.sailing.domain.tracking.TrackedRegatta;
10
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
11 11
import com.sap.sailing.domain.tracking.WindStore;
12 12
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
13 13
import com.sap.sailing.server.interfaces.RacingEventService;
java/com.sap.sailing.server.trackfiles.test/src/com/sap/sailing/server/trackfiles/test/JumpyTrackSmootheningTest.java
... ...
@@ -200,7 +200,8 @@ public class JumpyTrackSmootheningTest {
200 200
assertEquals(13, markPassings.size());
201 201
}
202 202
assertTrue(durationForAdjustedTrack.times(8).compareTo(durationForOriginalTrack) < 0,
203
- "Expected duration for mark passing analysis on adjusted track to be at least eight times less than for original track");
203
+ "Expected duration for mark passing analysis on adjusted track to be at least eight times less than for original track: "+
204
+ durationForAdjustedTrack+" vs. "+durationForOriginalTrack);
204 205
}
205 206
206 207
private DynamicGPSFixTrack<Competitor, GPSFixMoving> readTrack(String filename) throws Exception {
java/com.sap.sailing.server.trackfiles/META-INF/MANIFEST.MF
... ...
@@ -21,6 +21,7 @@ Import-Package: com.sap.sailing.domain.abstractlog,
21 21
com.sap.sailing.domain.abstractlog.regatta.events.impl,
22 22
com.sap.sailing.domain.common.sensordata,
23 23
com.sap.sailing.domain.racelogtracking,
24
+ com.sap.sailing.domain.shared.tracking,
24 25
com.sap.sse.security.shared,
25 26
javax.xml.bind,
26 27
javax.xml.bind.annotation,
java/com.sap.sailing.server.trackfiles/src/com/sap/sailing/server/trackfiles/impl/TrackReaderImpl.java
... ...
@@ -1,6 +1,6 @@
1 1
package com.sap.sailing.server.trackfiles.impl;
2 2
3
-import com.sap.sailing.domain.tracking.Track;
3
+import com.sap.sailing.domain.shared.tracking.Track;
4 4
import com.sap.sse.common.Timed;
5 5
6 6
class TrackReaderImpl<E, T extends Timed> implements TrackReader<E, T>, IterableLocker {
java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java
... ...
@@ -224,8 +224,9 @@ import com.sap.sailing.domain.regattalike.IsRegattaLike;
224 224
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
225 225
import com.sap.sailing.domain.regattalog.RegattaLogStore;
226 226
import com.sap.sailing.domain.resultimport.ResultUrlProvider;
227
+import com.sap.sailing.domain.shared.tracking.AddResult;
228
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
227 229
import com.sap.sailing.domain.statistics.Statistics;
228
-import com.sap.sailing.domain.tracking.AddResult;
229 230
import com.sap.sailing.domain.tracking.BravoFixTrack;
230 231
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
231 232
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
... ...
@@ -247,7 +248,6 @@ import com.sap.sailing.domain.tracking.TrackedRace;
247 248
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
248 249
import com.sap.sailing.domain.tracking.TrackedRegatta;
249 250
import com.sap.sailing.domain.tracking.TrackedRegattaListener;
250
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
251 251
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
252 252
import com.sap.sailing.domain.tracking.WindPositionMode;
253 253
import com.sap.sailing.domain.tracking.WindStore;
java/com.sap.sailing.server/src/com/sap/sailing/server/security/PermissionAwareRaceTrackingHandler.java
... ...
@@ -28,13 +28,13 @@ import com.sap.sailing.domain.common.security.SecuredDomainType;
28 28
import com.sap.sailing.domain.maneuverhash.ManeuverRaceFingerprintRegistry;
29 29
import com.sap.sailing.domain.markpassinghash.MarkPassingRaceFingerprintRegistry;
30 30
import com.sap.sailing.domain.racelog.RaceLogAndTrackedRaceResolver;
31
+import com.sap.sailing.domain.shared.tracking.TrackingConnectorInfo;
31 32
import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet;
32 33
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
33 34
import com.sap.sailing.domain.tracking.RaceTrackingHandler;
34 35
import com.sap.sailing.domain.tracking.RaceTrackingHandler.DefaultRaceTrackingHandler;
35 36
import com.sap.sailing.domain.tracking.TrackedRace;
36 37
import com.sap.sailing.domain.tracking.TrackedRegatta;
37
-import com.sap.sailing.domain.tracking.TrackingConnectorInfo;
38 38
import com.sap.sailing.domain.tracking.WindStore;
39 39
import com.sap.sse.common.Color;
40 40
import com.sap.sse.common.Duration;
java/com.sap.sailing.server/src/com/sap/sailing/server/simulation/SimulationServiceImpl.java
... ...
@@ -40,7 +40,7 @@ import com.sap.sailing.domain.common.WindSource;
40 40
import com.sap.sailing.domain.common.tracking.GPSFix;
41 41
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
42 42
import com.sap.sailing.domain.polars.PolarDataService;
43
-import com.sap.sailing.domain.tracking.AddResult;
43
+import com.sap.sailing.domain.shared.tracking.AddResult;
44 44
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
45 45
import com.sap.sailing.domain.tracking.MarkPassing;
46 46
import com.sap.sailing.domain.tracking.RaceListener;
java/com.sap.sailing.server/src/com/sap/sailing/server/statistics/TrackedRaceStatisticsCacheImpl.java
... ...
@@ -18,8 +18,8 @@ import com.sap.sailing.domain.common.Wind;
18 18
import com.sap.sailing.domain.common.WindSource;
19 19
import com.sap.sailing.domain.common.tracking.GPSFix;
20 20
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
21
+import com.sap.sailing.domain.shared.tracking.AddResult;
21 22
import com.sap.sailing.domain.tracking.AbstractTrackedRegattaAndRaceObserver;
22
-import com.sap.sailing.domain.tracking.AddResult;
23 23
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
24 24
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
25 25
import com.sap.sailing.domain.tracking.TrackedRace;
java/com.sap.sailing.simulator.test/META-INF/MANIFEST.MF
... ...
@@ -3,6 +3,7 @@ Bundle-ManifestVersion: 2
3 3
Bundle-Name: Test
4 4
Bundle-SymbolicName: com.sap.sailing.simulator.test
5 5
Bundle-Version: 1.0.0.qualifier
6
+Import-Package: com.sap.sailing.domain.shared.tracking
6 7
Bundle-Vendor: SAP
7 8
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
8 9
Require-Bundle: com.sap.sailing.simulator;bundle-version="1.0.0",
java/com.sap.sailing.simulator/META-INF/MANIFEST.MF
... ...
@@ -14,7 +14,8 @@ Require-Bundle: com.sap.sailing.domain.common;bundle-version="1.0.0",
14 14
org.apache.httpcomponents.httpclient;bundle-version="4.5.5",
15 15
org.json.simple;bundle-version="1.1.0"
16 16
Bundle-ActivationPolicy: lazy
17
-Import-Package: org.osgi.framework;version="1.6.0"
17
+Import-Package: com.sap.sailing.domain.shared.tracking,
18
+ org.osgi.framework;version="1.6.0"
18 19
Bundle-ClassPath: .
19 20
Export-Package: com.sap.sailing.simulator,
20 21
com.sap.sailing.simulator.impl,
java/com.sap.sailing.www/release_notes_admin.html
... ...
@@ -23,6 +23,25 @@
23 23
<div class="mainContent">
24 24
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
25 25
<div class="innerContent">
26
+ <h2 class="articleSubheadline">October 2025</h2>
27
+ <ul class="bulletList">
28
+ <li>When WindBots sent buffered data, e.g., from previous days, there was a possibility that
29
+ under some circumstances this data may have interfered with live data, leading, e.g.,
30
+ to "jumping" WindBot positions. This has now been fixed.<p>
31
+ <li>For the following system properties, alternative environment variables have been introduced:
32
+ <ul>
33
+ <li><tt>AWS_S3_TEST_S3ACCESSID</tt> for <tt>aws.s3.test.s3AccessId</tt></li>
34
+ <li><tt>AWS_S3_TEST_S3ACCESSKEY</tt> for <tt>aws.s3.test.s3AccessKey</tt></li>
35
+ <li><tt>GOOGLE_MAPS_AUTHENTICATION_PARAMS</tt> for <tt>google.maps.authenticationparams</tt></li>
36
+ <li><tt>YOUTUBE_V3_API_KEY</tt> for <tt>youtube.api.key</tt></li>
37
+ <li><tt>GEONAMES_ORG_USERNAMES</tt> for <tt>geonames.org.usernames</tt></li>
38
+ </ul>
39
+ If the system property is set, it takes precedence over the environment variable.</li>
40
+ </ul>
41
+ <h2 class="articleSubheadline">September 2025</h2>
42
+ <ul class="bulletList">
43
+ <li>Added boat classes "Elan E4" and "Beneteau First 36" to the list of boat classes.<p>
44
+ </ul>
26 45
<h2 class="articleSubheadline">August 2025</h2>
27 46
<ul class="bulletList">
28 47
<li>Pairings, e.g., in league set-ups, can not be copied across regattas/leaderboards.
java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/ChartToCsvExporter.java
... ...
@@ -1,7 +1,9 @@
1 1
package com.sap.sse.datamining.ui.client;
2 2
3
-import java.util.ArrayList;
4
-import java.util.List;
3
+import java.util.HashMap;
4
+import java.util.Map;
5
+import java.util.Map.Entry;
6
+import java.util.TreeMap;
5 7
6 8
import org.moxieapps.gwt.highcharts.client.Chart;
7 9
import org.moxieapps.gwt.highcharts.client.Point;
... ...
@@ -42,38 +44,33 @@ public class ChartToCsvExporter {
42 44
private static String createCsvExportContentForStatisticsCurve(Chart chartToExport) {
43 45
if (chartToExport != null && chartToExport.getSeries().length > 0) {
44 46
final StringBuilder csvStr = new StringBuilder("Series name");
45
- int minX = Integer.MAX_VALUE;
47
+ final Map<Number, Integer> columnIndexByXValue = new HashMap<>();
46 48
{
47
- final List<String> columnNames = new ArrayList<>();
49
+ final TreeMap<Number, String> orderedColumnNames = new TreeMap<>((a, b) -> Double.compare(a.doubleValue(), b.doubleValue()));
48 50
// collect column names across all series; some may not have points for all columns
49 51
for (final Series series : chartToExport.getSeries()) {
50
- for (final Point p : series.getPoints()) {
51
- if (p.getX().intValue() < minX) {
52
- minX = p.getX().intValue();
53
- }
54
- }
55 52
for (Point point : series.getPoints()) {
56
- final String pointName = point.getName();
57
- final String columnName = pointName != null && !pointName.isEmpty() ? pointName : point.getX().toString();
58
- while (columnNames.size() <= point.getX().intValue()-minX) {
59
- columnNames.add(columnNames.size(), null);
60
- }
61
- columnNames.set(point.getX().intValue()-minX, columnName);
53
+ final String columnName = getColumnName(point);
54
+ orderedColumnNames.put(point.getX(), columnName);
62 55
}
63 56
}
64
- for (final String columnName : columnNames) {
57
+ for (final String columnName : orderedColumnNames.values()) {
65 58
csvStr.append(';');
66 59
if (columnName != null && !columnName.isEmpty()) {
67 60
csvStr.append(columnName);
68 61
}
69 62
}
63
+ int columnIndex = 0;
64
+ for (final Entry<Number, String> e : orderedColumnNames.entrySet()) {
65
+ columnIndexByXValue.put(e.getKey(), columnIndex++);
66
+ }
70 67
}
71 68
csvStr.append("\r\n");
72 69
for (Series series : chartToExport.getSeries()) {
73 70
csvStr.append(series.getName());
74 71
int lastXIndex = -1;
75 72
for (Point point : series.getPoints()) {
76
- while (lastXIndex < point.getX().intValue()-minX) {
73
+ while (lastXIndex < columnIndexByXValue.get(point.getX())) {
77 74
csvStr.append(';');
78 75
lastXIndex++;
79 76
}
... ...
@@ -87,4 +84,10 @@ public class ChartToCsvExporter {
87 84
return "Statistics are empty";
88 85
}
89 86
87
+ private static String getColumnName(Point point) {
88
+ final String pointName = point.getName();
89
+ final String columnName = pointName != null && !pointName.isEmpty() ? pointName : point.getX().toString();
90
+ return columnName;
91
+ }
92
+
90 93
}
java/com.sap.sse.filestorage/src/com/sap/sse/filestorage/testsupport/AmazonS3TestSupport.java
... ...
@@ -8,17 +8,20 @@ import com.sap.sse.filestorage.impl.BaseFileStorageServiceImpl;
8 8
import com.sap.sse.security.SecurityService;
9 9
10 10
/**
11
- * Provide the S3 credentials for an IAM account that has access to the "sapsailing-automatic-upload-test" bucket
12
- * in the system properties {@code aws.s3.test.s3AccessId} and {@code aws.s3.test.s3AccessKey}. For the build
13
- * script in {@code configuration/buildAndUpdateProduct.sh} this can be done by setting the {@code APP_PARAMETERS}
14
- * environment variable for the script like this: {@code APP_PARAMETERS="-Daws.s3.test.s3AccessId=... -Daws.s3.test.s3AccessKey=..."}
11
+ * Provide the S3 credentials for an IAM account that has access to the "sapsailing-automatic-upload-test" bucket in the
12
+ * system properties {@code aws.s3.test.s3AccessId} and {@code aws.s3.test.s3AccessKey}. For the build script in
13
+ * {@code configuration/buildAndUpdateProduct.sh} this can be done by setting the {@code APP_PARAMETERS} environment
14
+ * variable for the script like this:
15
+ * {@code APP_PARAMETERS="-Daws.s3.test.s3AccessId=... -Daws.s3.test.s3AccessKey=..."}. Alternatively, e.g., in order to
16
+ * avoid system property setting with the "-D" command line option to be shown in log files, you may pass the ID and key
17
+ * as environment variables {@code AWS_S3_TEST_S3ACCESSID} and {@code AWS_S3_TEST_S3ACCESSKEY}, respectively.
15 18
*
16 19
* @author Axel Uhl (d043530)
17 20
*
18 21
*/
19 22
public class AmazonS3TestSupport {
20
- public static final String s3AccessId = System.getProperty("aws.s3.test.s3AccessId");
21
- public static final String s3AccessKey = System.getProperty("aws.s3.test.s3AccessKey");
23
+ public static final String s3AccessId = System.getProperty("aws.s3.test.s3AccessId", System.getenv("AWS_S3_TEST_S3ACCESSID"));
24
+ public static final String s3AccessKey = System.getProperty("aws.s3.test.s3AccessKey", System.getenv("AWS_S3_TEST_S3ACCESSKEY"));
22 25
private static final String s3BucketName = "sapsailing-automatic-upload-test";
23 26
24 27
public static BaseFileStorageServiceImpl createService(final SecurityService securityService) throws InvalidPropertiesException, IOException {
java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/AwsLandscapeImpl.java
... ...
@@ -155,6 +155,7 @@ import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTarg
155 155
import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetGroupsResponse;
156 156
import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetHealthRequest;
157 157
import software.amazon.awssdk.services.elasticloadbalancingv2.model.DescribeTargetHealthResponse;
158
+import software.amazon.awssdk.services.elasticloadbalancingv2.model.IpAddressType;
158 159
import software.amazon.awssdk.services.elasticloadbalancingv2.model.Listener;
159 160
import software.amazon.awssdk.services.elasticloadbalancingv2.model.LoadBalancer;
160 161
import software.amazon.awssdk.services.elasticloadbalancingv2.model.LoadBalancerAttribute;
... ...
@@ -348,6 +349,7 @@ public class AwsLandscapeImpl<ShardingKey> implements AwsLandscape<ShardingKey>
348 349
final CreateLoadBalancerResponse response = client
349 350
.createLoadBalancer(CreateLoadBalancerRequest.builder()
350 351
.name(name)
352
+ .ipAddressType(IpAddressType.DUALSTACK) // IPv4 and IPv6
351 353
.subnetMappings(subnetMappings)
352 354
.securityGroups(getDefaultSecurityGroupForApplicationLoadBalancer(region).getId())
353 355
.build());
java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/AwsApplicationConfiguration.java
... ...
@@ -58,9 +58,6 @@ implements UserDataProvider {
58 58
* <li>The {@link #setMailSmtpPort(int) SMTP port} defaults to 25.</li>
59 59
* <li>The {@link #setMailSmtpHost(String) mail SMTP host} defaults to <tt>email-smtp.${region}.amazonaws.com</tt>.</li>
60 60
* <li>The {@link #setMailSmtpAuth(boolean) mail SMTP authentication} is activated by default.</li>
61
- * <li>The {@link #setMailSmtpUser(String) mail SMTP user} defaults to {@code AKIAIHCQEFAZDLIK7SUQ} which is the
62
- * project's AWS SES (Simple e-Mail Service) username. The {@link #setMailSmtpPassword(String) password}, however,
63
- * must explicitly be provided and does not default to any non-{@code null}, non-empty value.</li>
64 61
* <li>The {@link #setMemoryInMegabytes(int) memory size} allocated to the process defaults to what {@link #setMemoryTotalSizeFactor(int)}
65 62
* sets, or, if {@link #setMemoryTotalSizeFactor(int)} isn't used either, to 3/4 of the physical memory available
66 63
* on the host running the application, minus some baseline allocated to the operating system.</li>
... ...
@@ -148,7 +145,6 @@ implements UserDataProvider {
148 145
* The pattern requires the region ID as {@link String} parameter and produces an AWS SES SMTP hostname for that region.
149 146
*/
150 147
private static final String DEFAULT_SMTP_HOSTNAME_PATTERN = "email-smtp.%s.amazonaws.com";
151
- private static final String DEFAULT_SMTP_USER = "AKIAIHCQEFAZDLIK7SUQ";
152 148
private static final Integer DEFAULT_SMTP_PORT = 25;
153 149
private AwsLandscape<ShardingKey> landscape;
154 150
private AwsRegion region;
... ...
@@ -388,7 +384,9 @@ implements UserDataProvider {
388 384
userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_HOST, mailSmtpHost==null?getDefaultAwsSesMailHostForRegion():mailSmtpHost);
389 385
userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_PORT, mailSmtpPort==null?DEFAULT_SMTP_PORT.toString():mailSmtpPort.toString());
390 386
userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_AUTH, mailSmtpAuth==null?Boolean.TRUE.toString():mailSmtpAuth.toString());
391
- userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_USER, mailSmtpUser==null?DEFAULT_SMTP_USER:mailSmtpUser);
387
+ if (mailSmtpUser != null) {
388
+ userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_USER, mailSmtpUser);
389
+ }
392 390
if (mailSmtpPassword != null) {
393 391
userData.put(DefaultProcessConfigurationVariables.MAIL_SMTP_PASSWORD, mailSmtpPassword);
394 392
}
java/com.tractrac.clientmodule/META-INF/MANIFEST.MF
... ...
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
2 2
Bundle-ManifestVersion: 2
3 3
Bundle-Name: TracTrac Client module
4 4
Bundle-SymbolicName: com.tractrac.clientmodule
5
-Bundle-Version: 4.0.3
5
+Bundle-Version: 4.0.4
6 6
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
7 7
Bundle-ClassPath: .,
8 8
lib/TracAPI.jar
java/com.tractrac.clientmodule/README.txt
... ...
@@ -16,6 +16,18 @@ It contains also some files:
16 16
- Manifest.txt -> manifest used to create the test.jar file
17 17
18 18
********************************************
19
+ TracAPI 4.0.4
20
+********************************************
21
+This is the final version. It keeps the backward compatibility. This version has been compiled with
22
+Java 21 but the target compatibility continues being Java 8.
23
+
24
+ Release date: 01/10/2025
25
+
26
+ 1) Bugs
27
+
28
+ - The heartbeat monitoring the connection status can stop working (Reported by Jorge Piera, 01/10/2025)
29
+
30
+********************************************
19 31
TracAPI 4.0.3
20 32
********************************************
21 33
This is the final version. It keeps the backward compatibility. The new parameter files will be encoded in UTF-8,
java/com.tractrac.clientmodule/lib/TracAPI-src.jar
java/com.tractrac.clientmodule/lib/TracAPI.jar
java/com.tractrac.clientmodule/pom.xml
... ...
@@ -8,6 +8,6 @@
8 8
<version>1.0.0-SNAPSHOT</version>
9 9
</parent>
10 10
<artifactId>com.tractrac.clientmodule</artifactId>
11
- <version>4.0.3</version>
11
+ <version>4.0.4</version>
12 12
<packaging>eclipse-plugin</packaging>
13 13
</project>
java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/Main.java
... ...
@@ -62,8 +62,8 @@ public class Main {
62 62
race
63 63
);
64 64
raceSubscriber.subscribeConnectionStatus(listener);
65
- raceSubscriber.subscribePositionedItemPositions(listener);
66
- //raceSubscriber.subscribePositions(listener);
65
+ //raceSubscriber.subscribePositionedItemPositions(listener);
66
+ raceSubscriber.subscribePositions(listener);
67 67
//raceSubscriber.subscribePositionsSnapped(listener);
68 68
raceSubscriber.subscribeControlPassings(listener);
69 69
//raceSubscriber.subscribeCompetitorSensorData(listener);
wiki/howto/eventmanagers/linking-race-videos.md
... ...
@@ -59,4 +59,4 @@ This articel provides a short guidance of how to synchronize races with their Yo
59 59
8. start again from step 3. of chaper 1
60 60
61 61
![Figure 4: Link all races covered by the video](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/linking%20race%20videos/multi_sync.png)
62
-**Figure 4: Link all races covered by the video**
... ...
\ No newline at end of file
0
+**Figure 4: Link all races covered by the video**
wiki/howto/onboarding.md
... ...
@@ -10,7 +10,7 @@ First of all, make sure you've looked at [http://www.amazon.de/Patterns-Elements
10 10
11 11
1. Git Account
12 12
13
- - The primary Git repository for the project is hosted on Github (see [https://github.com/SAP/sailing-analytics](https://github.com/SAP/sailing-analytics)). To clone, use ``git@github.com:SAP/sailing-analytics.git``. To gain write access you have to become member of the [sailing-analytics-team](https://github.com/orgs/SAP/teams/sailing-analytics-team) organization. For that you need to [link your Github user to the Github SAP organization](https://wiki.one.int.sap/wiki/display/ospodocs/Self-Service+for+Joining+an+SAP+GitHub+Organization). We still have a shadow repository around that, e.g., powers our Wiki at [https://wiki.sapsailing.com](https://wiki.sapsailing.com) and which lives at ``ssh://trac@sapsailing.com/home/trac/git``.
13
+ - The primary Git repository for the project is hosted on Github (see [https://github.com/SAP/sailing-analytics](https://github.com/SAP/sailing-analytics)). To clone, use ``git@github.com:SAP/sailing-analytics.git``. To gain write access you have to become member of the [sailing-analytics-team](https://github.com/orgs/SAP/teams/sailing-analytics-team) organization. For that you need to [link your Github user to the Github SAP organization](https://wiki.one.int.sap/wiki/display/ospodocs/Self-Service+for+Joining+an+SAP+GitHub+Organization). For that to work, your Github account needs to have your @sap.com e-mail address assigned and verified. We still have a shadow repository around that, e.g., powers our Wiki at [https://wiki.sapsailing.com](https://wiki.sapsailing.com) and which lives at ``ssh://trac@sapsailing.com/home/trac/git``.
14 14
15 15
- In case you'd like to get access to the external git at `ssh://trac@sapsailing.com/home/trac/git` please send your SSH public key to one of the project maintainers, requesting git access. Make sure to NOT generate the key using Putty. Putty keys don't work reliably under Linux and on Windows/Cygwin environments. Use ssh-keygen in a Cygwin or Linux or MacOS/X environment instead. For further instructions for generating an ssh-key see [GitHub](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).
16 16
Note: If you want to use the ssh-key in the context of our solution, it can be an RSA or ED25519 format. Example for creating a key: `ssh-keygen -t ed25519 -b 512 -C "test@test.com"`. Make sure to set a non-empty password for your key.
wiki/howto/tutorials/home.md
... ...
@@ -91,52 +91,142 @@
91 91
92 92
<h3>All Tutorials:</h3>
93 93
94
- <table>
95
- <tr>
96
- <td><a href="sailinganalytics/tracking-race-player.md">Tracking Race Player Overview</a></td>
97
- <td><a href="sailinganalytics/sign-up.md">Signing up for a user account</a></td>
98
- <td><a href="sailinganalytics/into-to-backend.md">Introduction to Analytics Backend</a></td>
99
- </tr>
100
- <tr>
101
- <td><a href="sailinganalytics/simple-event-creation.md">Create a simple event</a></td>
102
- <td><a href="sailinganalytics/video-tracking-management.md">Add &amp; align Youtube videos</a></td>
103
- <td><a href="sailinganalytics/create-regattas.md">Create complex regattas</a></td>
104
- </tr>
105
- <tr>
106
- <td><a href="sailinganalytics/competitors-data-management.md">Edit scores/results</a></td>
107
- <td><a href="sailinganalytics/importing-results.md">Importing results</a></td>
108
- <td><a href="sailinganalytics/adjust-mark-position.md">Adjust mark position</a></td>
109
- </tr>
110
- <tr>
111
- <td><a href="sailinganalytics/set-mark-position.md">Set mark position</a></td>
112
- <td><a href="sailinganalytics/configure-wind-settings.md">Configure wind settings</a></td>
113
- <td><a href="sailinganalytics/set-up-regattas.md">Set up multiple regattas</a></td>
114
- </tr>
115
- <tr>
116
- <td><a href="sailinganalytics/leaderboard-groups-explanation.md">Leaderboard groups explained</a></td>
117
- <td><a href="sailinganalytics/split-leaderboard.md">Split a leaderboard</a></td>
118
- <td><a href="sailinganalytics/course-areas.md">Working with course areas</a></td>
119
- </tr>
120
- <tr>
121
- <td><a href="sailinganalytics/sailing-race-manager.md">Work with Race Manager app</a></td>
122
- <td><a href="sailinganalytics/race-manager-connection.md">Connect Race Manager to event</a></td>
123
- <td><a href="sailinganalytics/edit-results.md">Editing results</a></td>
124
- </tr>
125
- <tr>
126
- <td><a href="sailinganalytics/set-matchrace-event.md">Set up matchrace event</a></td>
127
- <td><a href="sailinganalytics/security-system.md">Security system intro</a></td>
128
- <td><a href="sailinganalytics/default-creation-group.md">Default creation group</a></td>
129
- </tr>
130
- <tr>
131
- <td><a href="sailinganalytics/make-group-public.md">Make group public</a></td>
132
- <td><a href="sailinganalytics/publish-event.md">Publish an event</a></td>
133
- <td><a href="sailinganalytics/import-gpx-track.md">Import a GPX track</a></td>
134
- </tr>
135
- <tr>
136
- <td><a href="sailinganalytics/race-fixes.md">Fix device assignment</a></td>
137
- <td><a href="sailinganalytics/data-mining-tool.md">Data Mining Tool intro</a></td>
138
- <td></td>
139
- </tr>
94
+ <style>
95
+ table.guide-index { width: 100%; border-collapse: collapse; }
96
+ .guide-index col:first-child { width: 35%; }
97
+ .guide-index col:last-child { width: 65%; }
98
+ .guide-index td { padding: 8px 12px; vertical-align: top; }
99
+ .guide-index a { font-weight: 600; text-decoration: underline; }
100
+ /* optional zebra */
101
+ .guide-index tr:nth-child(odd) { background: #f7f7f7; }
102
+
103
+ /* optional: stack on small screens */
104
+ @media (max-width: 640px) {
105
+ .guide-index col { width: auto !important; }
106
+ .guide-index td { display: block; padding: 6px 12px; }
107
+ .guide-index td:first-child { padding-bottom: 0; }
108
+ }
109
+</style>
110
+
111
+<table class="guide-index">
112
+ <colgroup>
113
+ <col />
114
+ <col />
115
+ </colgroup>
116
+ <tbody>
117
+ <tr>
118
+ <td><a href="sailinganalytics/tracking-race-player.md">Tracking Race Player Overview</a></td>
119
+ <td>The video below gives an excellent overview of the main functionality of the SAP Race Player which is accessible on a per-race basis via the “Races/Tracking” tab of a regatta.</td>
120
+ </tr>
121
+ <tr>
122
+ <td><a href="sailinganalytics/sign-up.md">Signing up for a user account</a></td>
123
+ <td>This tutorial will show you how to sign up for a user account at the <strong>SAP Sailing Analytics</strong>.</td>
124
+ </tr>
125
+ <tr>
126
+ <td><a href="sailinganalytics/into-to-backend.md">Introduction to Analytics Backend</a></td>
127
+ <td>The guide <strong>General Introduction to SAP Sailing Analytics Backend</strong> will give you a look around the SAP Sailing Analytics <em>Admin Console</em>.</td>
128
+ </tr>
129
+ <tr>
130
+ <td><a href="sailinganalytics/simple-event-creation.md">Create a simple event</a></td>
131
+ <td>This tutorial shows you how to create an event with the SAP Analytics <em>Administration Console</em> on <a href="https://my.sapsailing.com/gwt/Home.html">my.sapsailing.com</a>.</td>
132
+ </tr>
133
+ <tr>
134
+ <td><a href="sailinganalytics/video-tracking-management.md">Add &amp; align Youtube videos</a></td>
135
+ <td>This tutorial shows how to add and align <em>Youtube videos</em> to the <strong>tracking timeline</strong>.</td>
136
+ </tr>
137
+ <tr>
138
+ <td><a href="sailinganalytics/create-regattas.md">Create complex regattas</a></td>
139
+ <td>This tutorial shows how to create <strong>more complex regattas</strong>.</td>
140
+ </tr>
141
+ <tr>
142
+ <td><a href="sailinganalytics/competitors-data-management.md">Edit scores/results</a></td>
143
+ <td>This tutorial shows how to <em>edit</em> scores/results in leaderboards and <em>change</em> competitor data in <strong>the SAP Sailing Analytics</strong>.</td>
144
+ </tr>
145
+ <tr>
146
+ <td><a href="sailinganalytics/importing-results.md">Importing results</a></td>
147
+ <td>The video explains how you can <em>import</em> results/scores from other regatta management systems for which <strong>the SAP Sailing Analytics</strong> have an integration in place, such as <strong>YachtScoring</strong> and <strong>Manage2Sail</strong>.</td>
148
+ </tr>
149
+ <tr>
150
+ <td><a href="sailinganalytics/adjust-mark-position.md">Adjust mark position</a></td>
151
+ <td>This episode shows you how to adjust <em>the position of a mark</em> with <strong>SAP Sailing Analytics</strong>.</td>
152
+ </tr>
153
+ <tr>
154
+ <td><a href="sailinganalytics/set-mark-position.md">Set mark position</a></td>
155
+ <td>This episode shows you how to set <em>the position of a mark</em> with <strong>SAP Sailing Analytics</strong> in case the tracker failed or you were not able to attach a <em>tracker</em> to the mark.</td>
156
+ </tr>
157
+ <tr>
158
+ <td><a href="sailinganalytics/configure-wind-settings.md">Configure wind settings</a></td>
159
+ <td>This tutorial shows how to configure <strong>wind settings.</strong>.</td>
160
+ </tr>
161
+ <tr>
162
+ <td><a href="sailinganalytics/set-up-regattas.md">Set up multiple regattas</a></td>
163
+ <td>This tutorial shows how to set up <em>multiple regattas</em> or <em>classes events</em> with the <strong>SAP Sailing Analytics</strong>.</td>
164
+ </tr>
165
+ <tr>
166
+ <td><a href="sailinganalytics/leaderboard-groups-explanation.md">Leaderboard groups explained</a></td>
167
+ <td>This tutorial explains how <em>Leaderboard Groups</em> work in <strong>the SAP Sailing Analytics</strong>.</td>
168
+ </tr>
169
+ <tr>
170
+ <td><a href="sailinganalytics/split-leaderboard.md">Split a leaderboard</a></td>
171
+ <td>This tutorial shows how to <em>split</em> a leaderboard into divisions with the <strong>SAP Sailing Analytics</strong>.</td>
172
+ </tr>
173
+ <tr>
174
+ <td><a href="sailinganalytics/course-areas.md">Working with course areas</a></td>
175
+ <td>This tutorial shows how to work with <em>course areas</em> in <strong>SAP Sailing Analytics</strong>.</td>
176
+ </tr>
177
+ <tr>
178
+ <td><a href="sailinganalytics/sailing-race-manager.md">Work with Race Manager app</a></td>
179
+ <td>This tutorial explains how to work with <strong>the SAP Sailing Race Manager Application</strong>.</td>
180
+ </tr>
181
+ <tr>
182
+ <td><a href="sailinganalytics/race-manager-connection.md">Connect Race Manager to event</a></td>
183
+ <td>This tutorial shows how to connect <strong>the Race Manager Application</strong> to an <em>event</em>.</td>
184
+ </tr>
185
+ <tr>
186
+ <td><a href="sailinganalytics/edit-results.md">Editing results</a></td>
187
+ <td>This tutorial explains how to <em>edit</em> results with <strong>the SAP Sailing Race Manager Application</strong>.</td>
188
+ </tr>
189
+ <tr>
190
+ <td><a href="sailinganalytics/set-matchrace-event.md">Set up matchrace event</a></td>
191
+ <td>This episode shows you how to set up a <em>matchrace event</em> with <strong>the SAP Sailing Analytics</strong>.</td>
192
+ </tr>
193
+ <tr>
194
+ <td><a href="sailinganalytics/security-system.md">Security system intro</a></td>
195
+ <td>This episode serves as an introduction to the <em>security system</em> of the <strong>SAP Sailing Analytics</strong>.</td>
196
+ </tr>
197
+ <tr>
198
+ <td><a href="sailinganalytics/default-creation-group.md">Default creation group</a></td>
199
+ <td>This episode shows how to set a default <em>creation group</em> in user details with <strong>the SAP Sailing Analytics</strong>.</td>
200
+ </tr>
201
+ <tr>
202
+ <td><a href="sailinganalytics/make-group-public.md">Make group public</a></td>
203
+ <td>This episode shows how to make everything your group owns <em>readable</em> for everyone in <strong>the SAP Sailing Analytics</strong>.</td>
204
+ </tr>
205
+ <tr>
206
+ <td><a href="sailinganalytics/publish-event.md">Publish an event</a></td>
207
+ <td>This episode shows how to <em>publish an event</em> through a dedicated user group in <strong>the SAP Sailing Analytics</strong>.</td>
208
+ </tr>
209
+ <tr>
210
+ <td><a href="sailinganalytics/import-gpx-track.md">Import a GPX track</a></td>
211
+ <td>This episode shows how to import a track from a <em>GPX file</em>.</td>
212
+ </tr>
213
+ <tr>
214
+ <td><a href="sailinganalytics/race-fixes.md">Fix device assignment</a></td>
215
+ <td>This episode shows how to <em>fix</em> races when a device is assigned to multiple competitors <strong>accidentally</strong>.</td>
216
+ </tr>
217
+ <tr>
218
+ <td><a href="sailinganalytics/data-mining-tool.md">Data Mining Tool intro</a></td>
219
+ <td>This tutorial gives an introduction to <strong>the SAP Sailing Analytics Data Mining Tool</strong>.</td>
220
+ </tr>
221
+ <tr>
222
+ <td><a href="sailinganalytics/copy-course-to-race.md">Copy course to race</a></td>
223
+ <td>This tutorial explains how to copy course to other race.</td>
224
+ </tr>
225
+ <tr>
226
+ <td><a href="sailinganalytics/dset-up-course.md">Course set up</a></td>
227
+ <td>This tutorial explains how to set up course in <strong>AdminConsole</strong>.</td>
228
+ </tr>
229
+ </tbody>
140 230
</table>
141 231
142 232
<hr>
wiki/howto/tutorials/sailinganalytics/adjust-mark-position.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows you how to adjust *the postion of a mark* with **SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488483711" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010892770-68eafb9d9777a29688f3fa3dd35f2c82574a8072d69f1ae144ef4a56d7835562-d?f=webp&region=us" alt="Adjust the position of a mark with the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488483711" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/AdjustingMarkPositionByCreatingNewFixCut.mp4" type="video/mp4">
1
+ Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/competitors-data-management.md
... ...
@@ -1,19 +1,5 @@
1 1
This tutorial shows how to *edit* scores/results in leaderboards and *change* competitor data in **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488484658" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010898993-1939bb3284504da7d6ce0c7ca44c68bac843370b6b01723d78ee672bf2568701-d?f=webp&region=us" alt="How to edit scores/results in leaderboards and change competitor data in the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488484658" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
- <br><br><br>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/editingcompetitors.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/configure-wind-settings.md
... ...
@@ -1,19 +1,5 @@
1 1
This tutorial shows how to configure **wind settings** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488860317" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1011532573-a72305c16fcb48b67ff30dd60ffb19612a7dc407c852424da37cd49cdc238351-d?f=webp&region=us" alt="Configuring wind settings" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em;">
8
- <a href="https://vimeo.com/488860317" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
3
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/ConfiguringWindSettings.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
wiki/howto/tutorials/sailinganalytics/copy-course-to-race.md
... ...
@@ -0,0 +1,5 @@
1
+This tutorial explains how to copy course to other race with **the SAP Sailing Race Manager Application** :
2
+
3
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/CopyingCourseToOtherRaces.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/course-areas.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to work with *course areas* in **SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488486330" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010908534-67799518b2812965dddb3595fdf0452eae2e61346ae69213748313856a6f3b96-d?f=webp&amp;region=us" alt="Working with course areas SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488486330" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/WorkingWithCourseAreas_Part1.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/create-regattas.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to create **more complex regattas** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/634988326" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1277334038-e9c29f5bf85d54076b7aacb78786efdc39b079d048a97f71a?f=webp&region=us" alt="Creating more complex regattas (with multiple series)" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/634988326" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
3
+<video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/CreateMoreComplexRegattas.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/data-mining-tool.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial gives an introduction to **the SAP Sailing Analytics Data Mining Tool** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/517202211" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1070935518-8b0ba12a0b853e140c36fd9dd89b1fe4929bdab5d4e464017628f508588e650d-d?f=webp&region=us" alt="An Introduction to the SAP Sailing Analytics Data Mining Tool" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/517202211" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/DataMiningIntroduction.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/default-creation-group.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows how to set a default *creation group* in user details with **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488485747" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010869111-2290caea7e6f6cac1a64b58af3423fc31700f24c2839a92c5e3dc7dd85c1e669-d?f=webp&region=us" alt="Setting a default creation group in user details with the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488485747" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/SettingDefaultCreationGroupInUserDetails.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/edit-results.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial explains how to *edit* results with **the SAP Sailing Race Manager Application** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488484797" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010874720-12e724c4940d378318204f96d75247de239f0728a9bd5beb5415aad70df1d2ca-d?f=webp&region=us" alt="Editing results with the SAP Sailing Race Manager app" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488484797" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/editingresultswiththeracemanagerapp.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/import-gpx-track.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows how to import a track from a *GPX file* :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/517202462" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1070949814-e607721644fc5d9ed274d3fb23342634b75aefe69ef34b387095b86c8b4008ad-d?f=webp&region=us" alt="Import a track from a GPX file" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/517202462" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/ImportGPXTrack.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/importing-results.md
... ...
@@ -1,18 +1,5 @@
1 1
The video explains how you can *import* results/scores from other regatta management systems for which **the SAP Sailing Analytics** have an integration in place, such as **YachtScoring** and **Manage2Sail**:
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/539278602" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1116473176-be00159b5f0aae985461bb734efb6adc52f27e5536d0b8f8960dde6192b6b66d-d?f=webp&amp;region=us" alt="Importing Results" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/539278602" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/VideoEditLeaderBoard.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/into-to-backend.md
... ...
@@ -1,18 +1,5 @@
1 1
The guide **General Introduction to SAP Sailing Analytics Backend** will give you a look around the SAP Sailing Analytics *Admin Console* :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://vimeo.com/488483856" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010905634-646b489c3740b5d1c6d1e83fe88fbe50cd43be60a2073d209520983a4c4ce9c2-d?f=webp&region=us" alt="General Introduction to SAP Sailing Analytics Backend" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488483856" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/ALookAroundAdminConsole.mp4" type="video/mp4">
1
+ Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/leaderboard-groups-explanation.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial explains how *Leaderboard Groups* work in **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/489321478" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1015856109-bb64efa3a2a4f304576789690646aaad0a7646c37c85c013ab43e016899e1aa1-d?f=webp&region=us" alt="Leaderboard groups explained in the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/489321478" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/Advanced+Topics/Leaderboard+Group+explained.mp4" type="video/mp4">
1
+ Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/make-group-public.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows how to make everything your group owns *readable* for everyone in **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488485239" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010886232-bf31fae06774e2abed01713779a86d50704dbf34ce2341c9bfe3751468c64d20-d?f=webp&amp;region=us" alt="Make everything your group owns readable for everyone in the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488485239" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/MakingEverythingYourGroupOwnsReadableForEveryone.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/publish-event.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows how to *publish an event* through a dedicated user group in **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488485433" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010883117-c1919ac1563d2d40fc5a6ff49f4ff66918ef1de005d0820a928d5478f0b98a64-d?f=webp&region=us" alt="Publish an event through a dedicated user group in the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488485433" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/PublishingEventThroughDedicatedUserGroup.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/race-fixes.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows how to *fix* races when a device is assigned to multiple competitors **accidentally** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/560018855" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1157667852-8e3dae0f1479eee9c82e71f93a5b0a2b09b6065a406b11027d3fbff828fd03f5-d?f=webp&region=us" alt="Fixing races when a device is assigned to multiple competitors accidentally" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/560018855" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/FixRaces.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/race-manager-connection.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to connect **the Race Manager Application** to an *event* :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/496362004" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1027112686-11119a6afa84a2196b43d0952cd504e43ab27ed67b256f138dac4481d9568cbf-d?f=webp&region=us" alt="How to connect the Race Manager App to an event" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/496362004" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/ConnectToEvent.mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/sailing-race-manager.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial explains how to work with **the SAP Sailing Race Manager Application** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488484868" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010878230-c2c61789493de4080b910dbb08521af8ff8f7036aa66c9b1eabe51690a68e5a0-d?f=webp&region=us" alt="How to work with the SAP Sailing Race Manager app" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488484868" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/howtogetgoingwiththeracemanagerapp.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/security-system.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode serves as an introduction to the *security system* of the **SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488484953" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010855456-9066a0a6e5a3c8d8fb4a5804e04ece6688246ac7d34932ca3e68d4e51a70fffd-d?f=webp&region=us" alt="Introduction to the security system of the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488484953" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/IntroductionSecuritySystem.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/set-mark-position.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows you how to set *the postion of a mark* with **SAP Sailing Analytics** in case the tracker failed or you were not able to attach a *tracker* to the mark :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/488486072" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010865518-60ba1a02a2fadb08c1f15bc00e8ad6fc5549c45abc521ca268ba291157e95480-d?f=webp&region=us" alt="Set the position of a mark with the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488486072" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/SettingFirstPositionForAMarkOnTheMapCut.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/set-matchrace-event.md
... ...
@@ -1,18 +1,5 @@
1 1
This episode shows you how to set up a *matchrace event* with **the SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/489321685" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1015853549-810174185dc750b52c9b061ff92f1579424fa5f89eeab0d12ef53ac6b4bc2cc5-d?f=webp&region=us" alt="Setting up a matchrace event with the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/489321685" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/Advanced+Topics/MatchraceSetup.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/set-up-course.md
... ...
@@ -0,0 +1,5 @@
1
+This tutorial explains how to set up course in *AdminConsole*:
2
+
3
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/SettingUpCourseInAdminConsole.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/set-up-regattas.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to set up *multiple regattas* or *classes events* with the **SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/489321331" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1015852481-18078496a5c92d5a7c894e3fa44678686e719e304baa3b0af31c976d1419e6d2-d?f=webp&amp;region=us" alt="Setting up multiple regattas or classes events with the SAP Sailing Analytics" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/489321331" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/Advanced+Topics/Setting+up+Events+with+multiple+Regattas+or+Classes.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/sign-up.md
... ...
@@ -1,20 +1,6 @@
1 1
This tutorial will show you how to sign up for a user account at the **SAP Sailing Analytics** :
2 2
3
-
4
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
5
- <a href="https://vimeo.com/488486248" target="_blank">
6
- <img src="https://i.vimeocdn.com/video/1010859699-4efcb1ff2d25befb03951c2ed835ca7e6dc07906902a6b80d832d1e8dcc6b74b-d?f=webp&region=us" alt="Signing up for a user account at the SAP Sailing Analytics" style="display: inline-block;">
7
- </a>
8
- <div style="line-height: normal; margin-top: -18em;margin-bottom: 16em ">
9
- <a href="https://vimeo.com/488486248" target="_blank" style="
10
- display: inline-block;
11
- vertical-align: middle;
12
- background-color: #007BFF;
13
- color: white;
14
- padding: 10px 20px;
15
- border-radius: 4px;
16
- text-decoration: none;
17
- font-weight: bold;
18
- ">Watch the Video</a>
19
- </div>
3
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/SigningUpForAUserAccount.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
20 6
wiki/howto/tutorials/sailinganalytics/simple-event-creation.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows you how to create an event with the SAP Analytics *Administration Console* on [my.sapsailing.com](https://my.sapsailing.com/gwt/Home.html) :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://vimeo.com/488484401" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010901946-6ef9df5238d5f517e69b301fc9eddb2069c59f1a41218f17539bfc5372fa685b-d?f=webp&region=us" alt="create an event with the SAP Analytics Administration Console" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/488484401" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/CreatingYourFirstEvent.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/split-leaderboard.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to *split* a Leaderboard into divisions with the **SAP Sailing Analytics** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/555773483" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1148490178-0abd3500b3e9f273c259bf4e690dd3ff4fd59d24fc615c5f03db1659ee232a6a-d?f=webp&region=us" alt="Splitting a Leaderboard into divisions" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/555773483" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/SplitLeaderBoard.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/tracking-race-player.md
... ...
@@ -1,19 +1,5 @@
1 1
The video below gives an excellent overview of the main functionality of the SAP Race Player which is accessible on a per-race basis via the "Races/Tracking" Tab of a regatta.
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 18em;">
4
- <a href="https://player.vimeo.com/video/786236240?h=5837f0d8b3" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1580518450-d061bcba753035fe130227e4b1c3ac735440041fabb61c3a14e289b9cd102dd5-d?mw=640&q=85" alt="tracking race player" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -12em; margin-bottom: 16em">
8
- <a href="https://player.vimeo.com/video/786236240?h=5837f0d8b3" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
3
+<video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/RaceBoardDemo.mp4" type="video/mp4">
4
+  Your browser does not support the video tag.
5
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinganalytics/video-tracking-management.md
... ...
@@ -1,18 +1,5 @@
1 1
This tutorial shows how to add and align *Youtube videos* to the **tracking timeline** :
2 2
3
-<div style="text-align: center; line-height: 0;">
4
- <a href="https://vimeo.com/634989080" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1277334324-ee19c413fefcee3578a6b2e395e45cd678e31de2d3299670b?f=webp&region=us" alt="Adding and aligning Youtube videos to the tracking timeline" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em">
8
- <a href="https://vimeo.com/634989080" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/AddingYouTubeVideos.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailingracemanager/configure-wind-settings.md
... ...
@@ -1,19 +1,5 @@
1
-This episode shows you how to configure *wind settings* with **the SAP Sailing Race Manager App** :
1
+This tutorial shows how to configure **wind settings** :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="thttps://vimeo.com/488860317" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1011532573-a72305c16fcb48b67ff30dd60ffb19612a7dc407c852424da37cd49cdc238351-d?f=webp&region=us" alt="How to configure wind settings" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em; margin-bottom: 16em;">
8
- <a href="hhttps://vimeo.com/488860317" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/ConfiguringWindSettings.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailingracemanager/connect-to-event.md
... ...
@@ -1,19 +1,5 @@
1 1
This episode shows you how to connect **the Race Manager App** to an *Event* :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://vimeo.com/496362004" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1027112686-11119a6afa84a2196b43d0952cd504e43ab27ed67b256f138dac4481d9568cbf-d?f=webp&region=us" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em;margin-bottom: 16em;">
8
- <a href="https://vimeo.com/496362004" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/Advanced+Topics/Leaderboard+Group+explained.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailingracemanager/edit-results.md
... ...
@@ -1,19 +1,5 @@
1 1
This episode shows you how to edit results with **the SAP Sailing Race Manager App** :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://vimeo.com/488484797" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010874720-12e724c4940d378318204f96d75247de239f0728a9bd5beb5415aad70df1d2ca-d?f=webp&region=us" alt="Editing results with the SAP Sailing Race Manager App" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em;margin-bottom: 16em;">
8
- <a href="https://vimeo.com/488484797" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
... ...
\ No newline at end of file
0
+ <video controls="true" width="640" src="https://sapsailing-documentation.s3-eu-west-1.amazonaws.com/adminconsole/editingresultswiththeracemanagerapp.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailingracemanager/sailing-race-guide.md
... ...
@@ -1,19 +1,5 @@
1 1
This episode shows you how to work with **the SAP Sailing Race Manager App** :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://vimeo.com/488484868" target="_blank">
5
- <img src="https://i.vimeocdn.com/video/1010878230-c2c61789493de4080b910dbb08521af8ff8f7036aa66c9b1eabe51690a68e5a0-d?f=webp&region=us" alt="How to work with the SAP Sailing Race Manager App" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -18em;margin-bottom: 16em;">
8
- <a href="https://vimeo.com/488484868" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
... ...
\ No newline at end of file
0
+<video controls="true" width="640" src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/adminconsole/WorkWithSailingManagerApp.mp4" type="video/mp4">
1
+  Your browser does not support the video tag.
2
+</video>
... ...
\ No newline at end of file
wiki/howto/tutorials/sailinsight/boat-team-creation.md
... ...
@@ -2,23 +2,13 @@ This guide will show you how to create your *first boat* or manage multiple boat
2 2
3 3
**Sail Insight** allows you to sail with multiple boats or teams. When you first set up **Sail Insight**, we ask you to set up your first boat, but you can edit it or create more at any time.
4 4
5
-<div style="text-align: center; line-height: 0;margin-bottom: 13em;">
6
- <a href="https://www.youtube.com/watch?v=rbdmoTf7w64$t=1s" target="_blank">
7
- <img src="https://img.youtube.com/vi/rbdmoTf7w64/0.jpg" alt="Boat and team creation in Sail Insight" style="display: inline-block;">
8
- </a>
9
- <div style="line-height: normal; margin-top: -14em;">
10
- <a href="https://www.youtube.com/watch?v=rbdmoTf7w64$t=1s" target="_blank" style="
11
- display: inline-block;
12
- vertical-align: middle;
13
- background-color: #007BFF;
14
- color: white;
15
- padding: 10px 20px;
16
- border-radius: 4px;
17
- text-decoration: none;
18
- font-weight: bold;
19
- ">Watch the Video</a>
20
- </div>
21
-</div>
5
+<video
6
+ controls="true"
7
+ src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/SailInsight/Boats+and+Team+Creation+%5BrbdmoTf7w64%5D.webm"
8
+ type="video/webm"
9
+ style="display:block;margin:1rem auto;max-width:100%;width:340px;height:auto;">
10
+ Your browser does not support the video tag.
11
+</video>
22 12
23 13
## Edit a boat
24 14
To edit a previsouly created boat, click the *"Account"* tab near the bottom right of the screen. Then click the *"My Boats"* menu item. Pick the boat that you previously created and edit the necesarry fields. Scroll down to press *"Save"*.
wiki/howto/tutorials/sailinsight/create-events.md
... ...
@@ -1,22 +1,12 @@
1 1
This guide will show you how to create an event with **Sail Insight**.
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://www.youtube.com/watch?v=f_sguquMRsQ" target="_blank">
5
- <img src="https://img.youtube.com/vi/f_sguquMRsQ/0.jpg" alt="How-to: Create an event with Sail Insight" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -14em;">
8
- <a href="https://www.youtube.com/watch?v=f_sguquMRsQ" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
3
+<video
4
+ controls="true"
5
+ src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/SailInsight/Create+an+Event+%5Bf_sguquMRsQ%5D.mp4"
6
+ type="video/mp4"
7
+ style="display:block;margin:1rem auto;max-width:100%;width:340px;height:auto;">
8
+ Your browser does not support the video tag.
9
+</video>
20 10
21 11
There are two ways of *placing marks*:
22 12
- Pair a mark with a real-world tracking device (another mobile phone)
wiki/howto/tutorials/sailinsight/getting-started.md
... ...
@@ -5,23 +5,13 @@ You can use **Sail Insight** without an account, if you have been invited to par
5 5
6 6
If you want to track your own trips or set up an event to sail with or against other people you will need to sign up for an account.
7 7
8
-<div style="text-align: center; line-height: 0;margin-bottom: 13em;">
9
- <a href="https://www.youtube.com/watch?v=ZnRooWkRvas" target="_blank">
10
- <img src="https://img.youtube.com/vi/ZnRooWkRvas/0.jpg" alt="Getting started with Sail Insight" style="display: inline-block;">
11
- </a>
12
- <div style="line-height: normal; margin-top: -14em;">
13
- <a href="https://www.youtube.com/watch?v=ZnRooWkRvas" target="_blank" style="
14
- display: inline-block;
15
- vertical-align: middle;
16
- background-color: #007BFF;
17
- color: white;
18
- padding: 10px 20px;
19
- border-radius: 4px;
20
- text-decoration: none;
21
- font-weight: bold;
22
- ">Watch the Video</a>
23
- </div>
24
-</div>
8
+<video
9
+ controls="true"
10
+ src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/SailInsight/Getting+started+with+Sail+Insight.+%5BZnRooWkRvas%5D.webm"
11
+ type="video/webm"
12
+ style="display:block;margin:1rem auto;max-width:100%;width:340px;height:auto;">
13
+ Your browser does not support the video tag.
14
+</video>
25 15
26 16
## How-to create an account
27 17
After opening **Sail Insight** create a new account by clicking on *"Register"*.
wiki/howto/tutorials/sailinsight/manage-marks.md
... ...
@@ -1,22 +1,13 @@
1 1
This episode shows you how to manage *marks* or pair them in **Sail Insight** :
2 2
3
-<div style="text-align: center; line-height: 0;margin-bottom: 14em;">
4
- <a href="https://www.youtube.com/watch?v=f3fjGuP-SbU" target="_blank">
5
- <img src="https://img.youtube.com/vi/f3fjGuP-SbU/0.jpg" alt="manage marks or pair them" style="display: inline-block;">
6
- </a>
7
- <div style="line-height: normal; margin-top: -14em;">
8
- <a href="https://www.youtube.com/watch?v=f3fjGuP-SbU" target="_blank" style="
9
- display: inline-block;
10
- vertical-align: middle;
11
- background-color: #007BFF;
12
- color: white;
13
- padding: 10px 20px;
14
- border-radius: 4px;
15
- text-decoration: none;
16
- font-weight: bold;
17
- ">Watch the Video</a>
18
- </div>
19
-</div>
3
+<video
4
+ controls="true"
5
+ src="https://sapsailing-documentation.s3.eu-west-1.amazonaws.com/SailInsight/Managing+Marks+%5Bf3fjGuP-SbU%5D.webm"
6
+ type="video/webm"
7
+ style="display:block;margin:1rem auto;max-width:100%;width:340px;height:auto;">
8
+ Your browser does not support the video tag.
9
+</video>
10
+
20 11
There are **two ways** of placing marks:
21 12
- Pair a mark with a real-world tracking device (another mobile phone)
22 13
- Give it a fixed location by providing latitude and longitude.
wiki/info/landscape/amazon-ec2.md
... ...
@@ -153,7 +153,7 @@ Any geo-blocking Web ACL that shall automatically be associated with ALBs that a
153 153
```
154 154
Note that in order to run this command you have to have valid credentials for the AWS region you're targeting with the request. Also consider using the ``--region`` argument if you're trying to tag a Web ACL in a region other than your AWS CLI's default region. Check your ``~/.aws/config`` file. Also see ``configuration/environments_scripts/repo/usr/local/bin/awsmfalogon.sh`` for logging on to the AWS CLI.
155 155
156
-### MongoDB Replica Setsn
156
+### MongoDB Replica Sets
157 157
158 158
There are currently three MongoDB replica sets:
159 159
wiki/info/landscape/aws-automation.md
... ...
@@ -328,7 +328,7 @@ The user can assign values to that variable that are then used as default propos
328 328
7. Configure target group health check
329 329
8. Register instance within target group
330 330
9. Create new rule within https listener that points to the correct target group
331
-10. Append „Use Event-SSL [domain] [eventId] 127.0.0.1 8888“ or „Use Home-SSL [domain] 127.0.0.1 8888“ to etc/httpd/conf.d/001-events.conf
331
+10. Append „Use Event-SSL \[domain\] \[eventId\] 127.0.0.1 8888“ or „Use Home-SSL \[domain\] 127.0.0.1 8888“ to etc/httpd/conf.d/001-events.conf
332 332
333 333
#### SAP instance on a shared EC2 instance
334 334
... ...
@@ -349,7 +349,7 @@ The user can assign values to that variable that are then used as default propos
349 349
15. Create target group with name „S-hared-instanceshortname“
350 350
16. Configuration of the target group health check with the server port of the sap instance
351 351
17. Create new rule within https listener that points to the correct target group
352
-18. Append „Use Event-SSL [domain] [eventId] 127.0.0.1 8888“ or „Use Home-SSL [domain] 127.0.0.1 8888“ to etc/httpd/conf.d/001-events.conf
352
+18. Append „Use Event-SSL \[domain\] \[eventId\] 127.0.0.1 8888“ or „Use Home-SSL \[domain\] 127.0.0.1 8888“ to etc/httpd/conf.d/001-events.conf
353 353
19. Check apache configuration with "apachectl configtest" and reload with "sudo service httpd reload“
354 354
355 355
#### SAP instance on a dedicated EC2 instance as a master
wiki/info/landscape/operating-system-upgrade.md
... ...
@@ -0,0 +1,131 @@
1
+# Operating System Upgrade Across Landscape
2
+
3
+[[_TOC_]]
4
+
5
+Mainly for security reasons we strive to keep the operating systems on which our EC2 instances are running up to date. This includes running the latest Linux kernels and having all packages updated to their latest versions as per the Amazon Linux or other Linux versions used. While doing so, we aim to keep service interruptions to a minimum and in particular keep services available at least in read-only mode also during upgrades.
6
+
7
+We distinguish between in-place upgrades without the need to re-boot, in-place upgrade requiring a reboot (e.g., due to Linux kernel updates), and upgrades that replace EC2 instances by new EC2 instances. The latter case can be sub-divided into cases where an incremental image upgrade can be used to produce a new version of an Amazon Machine Image (AMI) used for that instance type, and cases where a new from-scratch AMI set-up will be required. Also, the procedures to use depend on the type of service run on the instance that requires an upgrade.
8
+
9
+## Approaches for Operating System Updates
10
+
11
+### Using AdminConsole Landscape Management Panel
12
+
13
+The AdminConsole offers the Landscape Management panel (see, e.g., [https://security-service.sapsailing.com/gwt/AdminConsole.html#LandscapeManagementPlace:](https://security-service.sapsailing.com/gwt/AdminConsole.html#LandscapeManagementPlace:)) with a table entitled "Amazon Machine Images (AMIs)." It shows the different AMIs in use, among them the ``sailing-analytics-server``, the ``mongodb-server`` and the ``disposable-reverse-proxy`` images. Each of them have an "Upgrade" action icon in the "Actions" column that can be used to launch an instance off the image and then apply the steps necessary to upgrade the image to the latest version of kernel, all packages, and Java VM (if installed), then creates a new version of the AMI.
14
+
15
+See below for how to proceed with the upgraded images for the different image types.
16
+
17
+### Log On with SSH and Use Package Manager for Live Upgrade
18
+
19
+Instead of or in addition to upgrading the AMIs to new package and kernel versions, you can also log in to a running instance using SSH, and as root (using, e.g., ``sudo``) upgrade packages and kernel in place. Should a reboot be required, however, it depends on the particular instance you have been applying this to. Some instances should not simply be rebooted as this may unnecessarily reduce availability of some services and may not always lead to a clean recovery of all services after the reboot.
20
+
21
+For example, when rebooting an instance that runs one or more primary application processes for which replica processes run on other instances, inconsistencies between primary and replicas may result by a brute-force restart of the primary in some cases. See below for cleaner ways to do this.
22
+
23
+#### Amazon Linux
24
+
25
+We use Amazon Linux as the default for most instance types and hence most AMIs, particularly those for running the Sailing Analytics application, the MongoDB instances, the reverse proxy instances, and MariaDB for our Bugzilla service.
26
+
27
+Amazon Linux 2023 uses ``dnf`` as its package manager. An operating system upgrade is performed by running, as ``root`` (e.g., by logging in as ``ec2-user`` and then using ``sudo``):
28
+```
29
+ dnf --releasever=latest upgrade
30
+```
31
+This will upgrade all packages installed as well as the kernel. When run interactively, upgrade requiring a reboot will be displayed in the update list in red color. For scripted use, consider the ``needs-restarting -r`` command, delivering an exit status of ``1`` if a reboot is required.
32
+
33
+#### Debian
34
+
35
+Our use of Debian is currently restricted to running RabbitMQ which is a lot harder to install and configure on Amazon Linux.
36
+
37
+Debian uses ``apt`` as its package manager. Its default login user differs from Amazon Linux, where it is ``ec2-user`` and is called ``admin`` instead. Like the ``ec2-user`` on Amazon Linux, ``admin`` is eligible to use ``sudo`` to run commands with root privileges.
38
+
39
+Executing an update with apt works like this:
40
+```
41
+ apt-get update
42
+ apt-get upgrade
43
+```
44
+If this creates a file ``/var/run/reboot-required`` then the instance must be rebooted for all changes to take effect.
45
+
46
+## Upgrading the Different Instance Types
47
+
48
+### ``security-service.sapsailing.com`` Primary
49
+
50
+The corresponding ``security_service`` replica set usually has a single instance running only the primary application service. It offers a few ``Replicable``s that all other replica sets (except ``DEV``) replicate, such as the ``SecurityService`` and ``SharedSailingData`` services. It acts as a hub in particular for user, group, role and permission management. Other instances have their replicated versions of the service and can make decisions locally, sign in/up and authenticate users and manage their sessions locally. Replication through the ``security_service`` replica set serves the purpose of letting users roam about the landscape. Temporary outages of the ``security_service`` replica set will delay replication of these aspects across the landscape. However, transactions will not be lost but will be queued and applied when the service becomes available again.
51
+
52
+With this in mind, a restart of either the Java VM (in order to upgrade the application to a new version) or even a reboot of the EC2 instance, both typically done in less than 60s, will rarely cause effects noticeable to users. Therefore, we typically afford to upgrade the instance running the single primary process for the ``security_service`` replica set "in place:"
53
+
54
+- log on with ssh as ``ec2-user``
55
+- run ``dnf --releasever=latest upgrade``
56
+- if a reboot is required, reboot the instance
57
+
58
+It is useful to wait with the reboot until at least no known Sailing Analytics process start-up is happening which is in the middle of obtaining an initial load from the ``security_service`` replica set because this would be aborted and hence fail upon the reboot. Other than that, existing replicas will synchronize with the rebooted instance and the freshly started service once available again.
59
+
60
+Should you find good reasons against an in-place upgrade, make sure you have an upgraded ``sailing-analytics-server`` AMI, remove the running instance from the ``S-security-service`` and ``S-security-service-m`` target groups, launch a new instance off the upgraded AMI with the user data copied from the running instance, with only the ``INSTALL_FROM_RELEASE`` parameter upgrade to the latest release:
61
+```
62
+INSTALL_FROM_RELEASE=main-202502181141
63
+SERVER_NAME=security_service
64
+USE_ENVIRONMENT=security-service-master
65
+```
66
+
67
+Then add the new instance to the ``S-security-service`` and ``S-security-service-m`` target groups and terminate the old instance.
68
+
69
+### ``DEV``
70
+
71
+The ``DEV`` replica set is for testing only. Other than that, the instance runs our Hudson CI environment. Both are not expected to be highly available. Therefore, the same in-place update as for the ``security_service`` replica set is possible. For a clean Hudson shut-down, consider using [this link](https://hudson.sapsailing.com/quietDown).
72
+
73
+### ``ARCHIVE``
74
+
75
+Make sure you have an up-to-date ``sailing-analytics-server`` AMI. Then, see [[Upgrading ARCHIVE server|wiki/info/landscape/archive-server-upgrade]] for how to launch a new ARCHIVE candidate with that new AMI and how to switch to it once the loading of all races has finished successfully.
76
+
77
+### ``my``
78
+
79
+You can try an [in-place upgrade](#log-on-with-ssh-and-use-package-manager-for-live-upgrade) for these. Should this, however, require a reboot, you should then apply the following procedure:
80
+
81
+To start with, make sure you have an up-to-date ``sailing-analytics-server`` AMI (see above). Also make sure the auto-scaling group for the ``my`` replica set is set to use this latest AMI for any replicas launched by the auto-scaling group.
82
+
83
+The ``my`` replica set is special in comparison to most other replica sets. It runs its primary process on a dedicated instance and requires an instance type with at least 500GB of swap space. A good default is an ``i3.2xlarge`` instance type. The application settings, as of this writing, require 350GB of heap size, indicated by ``MEMORY="350000m"`` in the user data section for the instance.
84
+
85
+In order to move the ``my`` primary process to a new instance with a new operating system, use the AdminConsole's Landscape Management panel, and there the "Move master process to another instance" action. Make sure to select an appropricate ``i3....`` instance type with sufficient swap space, *not* the default ``C5_2_XLARGE`` suggestion. Explicitly enter the amount of memory you'd like to assign to the process, such as "350000" into the "Memory (MB)" field of the pop-up dialog, then confirm using the "OK" button.
86
+
87
+This will detach all running replicas (usually exactly one) from the primary process, remove the primary process from the ``S-my`` and ``S-my-m`` target groups, then stop and remove the primary process, which will also lead to the instance being terminated as this was the last (only) application process running on it. Then, a new instance off the latest AMI will be launched, deploying and starting a new primary process for the ``my`` replica set. Once this has loaded all contents from the DB and reports a healthy status, an explicit "Upgrade Replica" is launched which uses the explicit primary instance's IP address instead of the DNS host name to obtain an initial load. This works around the fact that so far the new primary hasn't been added to any target groups yet and hence isn't reachable under the ``my.sapsailing.com `` domain name.
88
+
89
+When the upgrade replica has reported a healthy status, the primary is added to the ``S-my`` and ``S-my-m`` target groups, and the upgrade replica is added to the ``S-my`` target group. Then, the old auto-replica which is expected to have been launched using the auto-scaling group will be terminated, causing the launching of a new instance to which a ``my`` replica is deployed and started. Once the auto-replica is healthy, the upgrade replica will be terminated which removes it from the ``S-my`` target group.
90
+
91
+### Sailing Analytics Multi-Servers
92
+
93
+You can try an [in-place upgrade](#log-on-with-ssh-and-use-package-manager-for-live-upgrade) for these. Should this, however, require a reboot, you should then apply the following procedure:
94
+
95
+To start with, make sure you have an up-to-date ``sailing-analytics-server`` AMI (see above). Also make sure that all auto-scaling groups for the application replica sets are set to use this latest AMI for any replicas launched by the auto-scaling group. (This can be achieved, e.g., using the AdminConsole's Landscape Management panel, and there the "Update machine image for auto-scaling replicas" button above the replica sets table.)
96
+
97
+Then, sort the replica sets table by the "Master Instance ID" column and identify the instances configured as "Multi-Server." Should this not be obvious, compare with the instances in the AWS EC2 console instance list named "SL Multi-Server". Click the "Move all application processes away from this replica set's master host to a new host" button. This will launch a new instance and move all application processes away from the old host, one by one. All replication aspects are handled automatically. The duration of migration varies depending on the content volumes hosted by the respective replica set. An empty replica sets migrates in a few minutes. Large replica sets may take an hour or more to migrate. The AdminConsole process may run into a timeout, but don't worry, the migration continues all the way to the end regardess of the web UI timeout.
98
+
99
+Still, should something take suspiciously long, maybe check the server logs of the server you used the Landscape Management panel on (usually ``security-service.sapsailing.com``). The ``logs/sailing.log.0`` file may show details of what went wrong or is taking long. Sometimes it may be the loading of one or more races that fails and doesn't let the instance report a healthy status. In such cases, a manual restart of that process may help, cd'ing into its folder and running ``./stop; ./start`` explicitly.
100
+
101
+### MariaDB
102
+
103
+This is a clear candidate for an in-place upgrade. Should a reboot be required, just reboot. It only takes about 10s, and for Bugzilla as system used mostly internally we can afford a 10s unavailability period.
104
+
105
+### RabbitMQ
106
+
107
+This is Debian-based. Try to go for an in-place upgrade. Should a reboot be required, ideally choose a time outside of major events and ongoing instance upgrades as those will require the RabbitMQ service to succeed.
108
+
109
+### MongoDB Replica Sets
110
+
111
+We currently have three MongoDB instances running in our EC2 landscape: ``[dbserver|mongo0|mongo1].internal.sapsailing.com``. The first hosts three ``mongod`` processes, for three separate replica sets: the single primary of the ``archive`` and the ``slow`` replica sets, and a hidden replica of the ``live`` replica set. The two other instance have primary/secondary ``mongod`` processes for the ``live`` replica set. Try an in-place upgrade first. If that doesn't require a reboot, you're done.
112
+
113
+If a reboot is required after an in-place upgrade, be gentle in how you carry out those reboots. The ``dbserver`` instance can be rebooted as long as no ARCHIVE server start-up is currently going on. During an ARCHIVE server start-up, failure to reach the database may lead to an incomplete state in the new ARCHIVE candidate which may require you to start over with the anyhow very time-consuming ARCHIVE start-up. The ``slow`` replica set and the hidden ``live`` replica, however, pose no obstacles regarding a reboot.
114
+
115
+For ``mongo0`` and ``mongo``, log on as ``ec2-user`` using SSH and use ``mongosh`` to see whether that instance is currently PRIMARY or SECONDARY. Reboot the SECONDARY first. When that has completed, SSH into the PRIMARY and in ``mongosh`` issue the command ``rs.stepDown()`` so that the PRIMARY becomes a SECONDARY, and the other instance that previously was a SECONDARY takes over as the new PRIMARY. With this courtesy, ongoing writing transactions will not even have to go through a re-try as you reboot the now SECONDAY instance.
116
+
117
+Should you choose to work with an upgraded ``mongodb-server`` AMI and the AdminConsole's Landscape Management panel, use the "Scale in/out" action on the respective MongoDB replica set to add new instances launched off the new AMI, then, once healthy, scale in to delete the old instances. This way you can handle the ``mongo0`` and ``mongo1`` instances. You should, however, have to adjust the DNS records in Route53 for ``mongo[01].internal.sapsailing.com`` to reflect the new instances because despite all tags-based resource discovery there are still some older configuration and environments files around that bootstrap new application instances by explicitly referring to ``mongo0.internal.sapsailing.com`` and ``mongo1.internal.sapsailing.com`` as their MongoDB instances to use.
118
+
119
+Should you need to upgrade the central ``dbserver.internal.sapsailing.com`` instance without an in-place upgrade, use the ``configuration/environments_scripts/central_mongo_setup/setup-central-mongo-instance.sh`` script to produce a new instance. When it has launched the new instance, it prints detailed instructions to its standard output for how to unmount the data volumes from the old and mount them to the new instance, as well as which DNS actions to take in Route53 and how to name and tag the new instance.
120
+
121
+### Central Reverse Proxy
122
+
123
+The central reverse proxy, currently running as ``sapsailing.com``, can typically be upgraded in-place. Should a re-boot be required, launch a disposable reverse proxy in the same availability zone as the central reverse proxy first, using the AdminConsole's Landscape Management panel with its "Reverse proxies" table and the corresponding "Add" button. Once that new disposable reverse proxy is shown as healthy in the corresponding ``CentralWebServerHTTP-Dyn`` target group you can reboot the central reverse proxy. It will lead to less than 30s of downtime regarding [https://bugzilla.sapsailing.com](https://bugzilla.sapsailing.com) and [https://wiki.sapsailing.com](https://wiki.sapsailing.com), as well as our self-hosted Git repository which is still used for back-up and cross-synchronization with the checked-out workspace for our Wiki.
124
+
125
+Should an in-place upgrade not be what you want, look into the ``configuration/environments_scripts/central_reverse_proxy`` folder with its setup scripts. They automate most parts of providing a new central reverse proxy instance that has been set up with the latest Amazon Linux from scratch. You will have to carry out a few steps manually, e.g., in the AWS Console, and the scripts will tell you in their standard output what these are.
126
+
127
+When done, terminate the additional disposable reverse proxy that you launched in the central reverse proxy's availability zone.
128
+
129
+### Disposable Reverse Proxy
130
+
131
+Make sure you have an upgraded ``disposable-reverse-proxy`` AMI, then use the AdminConsole Landscape Management panel and its "Reverse proxies" section to launch one or more disposable reverse proxies with the "Add" button. The default instance type suggested is usually a good fit. Make sure to launch one per availability zone in which you'd like to replace an old reverse proxy. When your new reverse proxy is healthy (this includes an error code 503 which only indicates that the reverse proxy is not in the same availability zone as the currently active production ARCHIVE server), terminate the corresponding old reverse proxy.
... ...
\ No newline at end of file
wiki/info/landscape/typical-development-scenarios.md
... ...
@@ -369,4 +369,4 @@ When TracTrac publishes a TracAPI upgrade, they usually notify us by e-mail and
369 369
370 370
After downloading, extract the ``lib/TracAPI.jar`` and ``src/TracAPI-src.jar`` from the ``TracAPI-x.y.z.tar.gz`` to ``java/com.tractrac.clientmodule/lib``. Unpack the ``TracAPI-x.y.z-javadoc.tar.gz`` into ``java/com.tractrac.clientmodule/javadoc`` and adjust the versions in ``META-INF/MANIFEST.MF`` and ``pom.xml`` accordingly. Unpack the ``Readme.txt`` file from ``TracAPI-x.y.z.tar.gz`` to ``java/com.tractrac.clientmodule``. Optionally, unpack the sample sources from the ``src/com`` folder contained in ``TracAPI-x.y.z.tar.gz`` into ``java/com.tractrac.clientmodule/src``.
371 371
372
-For non-trivial upgrades use of a git branch and dedicated build job is recommended. Otherwise, committing and pushing to master with the ``SAPSailingAnalytics-master`` build job picking up and testing the changes should suffice.
... ...
\ No newline at end of file
0
+For non-trivial upgrades use of a git branch and dedicated build job is recommended. Otherwise, committing and pushing to the ``main`` branch with the ``SAPSailingAnalytics-master`` build job picking up and testing the changes should suffice.
... ...
\ No newline at end of file
wiki/info/mobile/sailinsight-privacy-policy.md
... ...
@@ -0,0 +1,77 @@
1
+# Sail Insight Privacy Policy
2
+
3
+Sail Insight (or the “App”) is a mobile application that allows its users to manage, participate, and watch regattas. The App is licensed for publication by SAP S.E. to d-labs (datenschutz@d-labs.com, Jörn Hartwig, D‑LABS GmbH, Marlene-Dietrich-Allee 15, D-14482 Potsdam).
4
+
5
+This policy aims to help you better understand the App privacy processes and your rights in connection with your personal data. To sign up and use the App, you have to carefully read this policy and provide your consent for us to use your personal data in line with this policy.
6
+
7
+The term “personal data” or “personal information” used in this policy means any information relating to any natural person, who can be identified, directly or indirectly, in particular by reference to such information.
8
+
9
+## WHAT INFORMATION DOES THE APP COLLECT?
10
+The App collects and stores different types of personal data:
11
+
12
+- Data that is provided directly by you
13
+- Data produced during your usage of the App
14
+- Data received from the partners of the App and the third-parties
15
+
16
+User profile: The App collects data when you create or update your account. This may include name, email, and password.
17
+
18
+Data about boats: You may provide the App with the data about your boat (type, registration number, boat rating, scoring, scoring group) or upload it from an external website.
19
+
20
+Location data: Once you grant the App access to your geolocation (and only in-between the timepoints when the user selects “start tracking” and “stop tracking), it may collect location data from your mobile device. The location data is collected when the app is in the foreground and when it’s running in the background. If you don’t grant the App access to the geolocation data, this may affect some functionality of the App.
21
+
22
+Usage data: The App collects data about how you and other users interact with the application. This includes data such as regattas that you participated in, classes, marks, courses, races you’ve created. It may also include specific technical data such as your access dates and times, app features, or pages viewed, app crashes.
23
+
24
+Device data: the App may collect data about the devices used to access the application, operating systems and versions, software, preferred languages, and other system settings of your device.
25
+
26
+## HOW DO WE USE THE INFORMATION?
27
+Initiate, participate, and watch regattas. The App collects and uses personal data to allow you to use the functionality of the application, personalize, maintain, and improve it.
28
+
29
+Marketing. The App may use your personal data for marketing purposes, which may include communication with the user about newsletters, upcoming events, new functionality, promotions.
30
+
31
+Other communications. The App may inform you about changes in the policies, EULA services, or send additional information about the application that is not intended for marketing purposes.
32
+
33
+Research and development. We may use the data we collect for development, testing, research, to improve the user experience.
34
+
35
+## GROUNDS FOR PROCESSING
36
+The App collects and uses personal data based on lawful grounds, that generally include processing personal data to provide you and other users with services and features or/and upon your direct consent.
37
+
38
+## HOW LONG DO WE KEEP YOUR DATA?
39
+We may store and use anonymized data for our Research and Development activities and public regatta statistics. Request for deletion of any personal data shall be sent to support@sapsailing.com.
40
+
41
+## THIRD PARTIES THAT HAVE ACCESS TO YOUR PERSONAL DATA
42
+The App may share your personal information with its partners and other third parties.
43
+
44
+The App has specific agreements with third parties who have access to your personal data. Such agreements include sufficient measures (1) to safeguard your personal data, your rights, freedoms, legitimate interest with regard to such information, (2) to prevent abuse or unlawful access or transfer of your personal data.
45
+
46
+## WHO MAY HAVE ACCESS TO YOUR DATA?
47
+Additionally, we may disclose specific types of personal data to third parties in the following cases:
48
+
49
+- To the purchaser or seller of our business;
50
+- To the extent that we are required to do so by law;
51
+- In the enforcement and/or court procedure and/or when it is required to disclose to a governmental agency in response to a valid court order or subpoena or similar requirement of the court or other regulatory bodies of other countries;
52
+- To establish, exercise, or defend the legal rights of our business, employees, and authorized legal representatives.
53
+
54
+## WHERE DO WE STORE YOUR PERSONAL INFORMATION?
55
+Your personal data is being stored on AWS servers. You can read more about AWS GDPR policies here https://aws.amazon.com/compliance/gdpr-center/.
56
+
57
+The App is exercising and implementing all the sufficient and suitable technical and organizational safeguards and measures needed to provide secure processing and storage of your data in accordance with GDPR and trying to ensure that the third parties that receive your personal data also adhere to the GDPR.
58
+
59
+## YOUR RIGHTS
60
+Given that this version of the App is a beta release that includes separate specific functionality for limited distribution among selected users functionality that allows exercising the rights of the data subjects established by GDPR is not realised in full. The subsequent versions of the application as soon as they are available will include the full functionality that will ensure GDPR compliance of the application as listed below.
61
+
62
+- To request to amend/renew the inaccurate and/or incomplete personal data concerning you;
63
+- To delete your personal data, partially or fully
64
+- To restrict the processing of your personal data;
65
+- To object to the processing of your personal data for profiling to the extent that it is related to direct marketing;
66
+- To request to familiarize and access the personal data that was collected.
67
+- You may submit to us a request to exercise any of the abovementioned rights. We will review your request within 30 calendar days and notify you of the results of its consideration.
68
+- For any requests connected with the rights listed above please contact the person that invited you to test the application.
69
+
70
+## LINKS TO THE SITES OF THE THIRD PARTIES
71
+The App may, from time to time, contain links to websites of third parties. Please note that we are not responsible for the content, terms of use and/or service as well as for the privacy policies of these websites.
72
+
73
+## MODIFICATIONS AND AMENDMENTS
74
+We may amend this privacy policy at any time by posting a new version and sending you a separate notification via email. It will become effective at the time it will be published. If you continue to use the App, it will be regarded as an acceptance of the amended policy.
75
+
76
+## CONTACT
77
+If you have any complaints about the policy and your personal data, please contact us at support@sapsailing.com