Merge branch 'main' into bug6170

This commit is contained in:
Axel Uhl
2025-11-06 23:53:07 +01:00
23 changed files with 10113 additions and 41 deletions
@@ -0,0 +1,10 @@
---
name: File Issues on bugzilla.sapsailing.com
about: Please report bugs and issues on https://bugzilla.sapsailing.com instead
title: 'Do not report issues here. Please to go https://bugzilla.sapsailing.com'
labels: ''
assignees: ''
---
Dear user, as mentioned in our [README](https://github.com/SAP/sailing-analytics?tab=readme-ov-file#contributing) we have our issue tracker at [https://bugzilla.sapsailing.com](bugzilla.sapsailing.com). Please register there and file bugs, issues, or feature/enhancement requests there. Thank you.
+14
View File
@@ -0,0 +1,14 @@
# Issue Addressed by this PR
[Bug XXXX](https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=XXXX)
## Description
_FILL IN DETAILS HERE_
## Checklist for Pull Requests
- [ ] Supplied as many details as possible on this change
- [ ] Included the link to the associated Bugzilla issue in the Issue section above
- [ ] New code has been formatted using the [code formatter](https://github.com/SAP/sailing-analytics/blob/main/java/CodeFormatter.xml)
- [ ] The code has **unit tests** where applicable and is easily unit-testable
- [ ] This branch is appropriately named for the associated issue, usually `bugXXXX`
@@ -138,6 +138,10 @@ public class AIAgentImpl implements AIAgent {
chatSession = createChatSession();
} catch (UnsupportedOperationException | URISyntaxException | IOException | ParseException e) {
throw new RuntimeException(e);
} catch (SecurityException e) {
aiCore.setCredentials(null);
logger.warning("Invalid credentials; clearing (setting to null).");
throw e;
}
} else {
chatSession = null;
@@ -0,0 +1,72 @@
package com.sap.sailing.declination.impl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.GregorianCalendar;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.declination.DeclinationService;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sse.common.TimePoint;
public class WMMFileTest {
private static Geomagnetism g;
@BeforeAll
public static void setUpForClass() throws IOException, ParseException {
g = new Geomagnetism(new BufferedReader(new InputStreamReader(WMMFileTest.class.getClassLoader().getResourceAsStream("WMM2025.COF"))));
}
@Test
public void testWMM2025Reading() throws IOException {
assertNotNull(g);
}
@Test
public void testWMM2025Content1() throws IOException, ParseException {
final double lat = 44.123;
final double lng = 8.234;
final GregorianCalendar cal = new GregorianCalendar(2025, 10, 4, 0, 46, 9);
assertWMMEqualToOneTenthOfADegreeToDeclinationService(g, lat, lng, cal);
}
@Test
public void testWMM2025Content2() throws IOException, ParseException {
final double lat = 49.234;
final double lng = 9.777;
final GregorianCalendar cal = new GregorianCalendar(2025, 2, 7, 10, 22, 17);
assertWMMEqualToOneTenthOfADegreeToDeclinationService(g, lat, lng, cal);
}
@Test
public void testWMM2025ContentSouthernHemisphere() throws IOException, ParseException {
final double lat = -44.123;
final double lng = -8.234;
final GregorianCalendar cal = new GregorianCalendar(2026, 8, 1, 3, 26, 7);
assertWMMEqualToOneTenthOfADegreeToDeclinationService(g, lat, lng, cal);
}
@Test
public void testWMM2025ContentFarEastEquator() throws IOException, ParseException {
final double lat = 0.000;
final double lng = 170.444;
final GregorianCalendar cal = new GregorianCalendar(2027, 1, 24, 18, 0, 0);
assertWMMEqualToOneTenthOfADegreeToDeclinationService(g, lat, lng, cal);
}
private void assertWMMEqualToOneTenthOfADegreeToDeclinationService(final Geomagnetism g, final double lat,
final double lng, final GregorianCalendar cal) throws IOException, ParseException {
final double declinationWMM2025 = g.calculate(lng, lat, /* altitude */ 0, cal).getDeclination();
final DeclinationService s = DeclinationService.INSTANCE;
final Declination declinationFromService = s.getDeclination(TimePoint.of(cal.getTimeInMillis()), new DegreePosition(lat, lng), /* timeout */ 10000);
assertEquals(declinationFromService.getBearing().getDegrees(), declinationWMM2025, /* delta in degrees */ 0.15, "Deviation of more than 0.15 degrees; that's too much!");
}
}
@@ -13,7 +13,7 @@ import org.junit.jupiter.api.Test;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.declination.DeclinationService;
import com.sap.sailing.declination.impl.DeclinationImporter;
import com.sap.sailing.declination.impl.DeclinationServiceImpl;
import com.sap.sailing.declination.impl.DeclinationServiceImplWithStore;
import com.sap.sailing.domain.common.impl.CentralAngleDistance;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -23,7 +23,7 @@ public abstract class DeclinationServiceTest<I extends DeclinationImporter> exte
@BeforeEach
public void setUp() {
service = new DeclinationServiceImpl(new CentralAngleDistance(1./180.*Math.PI), importer);
service = new DeclinationServiceImplWithStore(new CentralAngleDistance(1./180.*Math.PI), importer);
}
@Test
@@ -0,0 +1,41 @@
package com.sap.sailing.declination.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.declination.impl.WMMCalculatorDeclinationService;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class WMMDeclinationServiceTest {
private static WMMCalculatorDeclinationService service;
private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
@BeforeAll
public static void setUp() {
service = new WMMCalculatorDeclinationService();
}
@Test
public void testSimpleDeclinationQueryMatchedInStore() throws IOException, ClassNotFoundException, ParseException {
Declination result = service.getDeclination(new MillisecondsTimePoint(simpleDateFormat.parse("2011-02-03").getTime()),
new DegreePosition(51, -5), /* timeoutForOnlineFetchInMilliseconds */ 3000);
assertEquals(-3.-14./60., result.getBearing().getDegrees(), 0.25);
}
@Test
public void testDeclinationQueryNotMatchedInStore() throws IOException, ClassNotFoundException, ParseException {
Declination result = service.getDeclination(new MillisecondsTimePoint(simpleDateFormat.parse("2020-02-03").getTime()),
new DegreePosition(51, -5), /* timeoutForOnlineFetchInMilliseconds */ 5000);
assertNotNull(result);
assertEquals(-1.531, result.getBearing().getDegrees(), 0.1);
}
}
@@ -0,0 +1,93 @@
2010.0 WMM-2010 12/10/2009
1 0 -29496.6 0.0 11.6 0.0
1 1 -1586.3 4944.4 16.5 -25.9
2 0 -2396.6 0.0 -12.1 0.0
2 1 3026.1 -2707.7 -4.4 -27.2
2 2 1668.6 -576.1 -10.0 -2.2
3 0 1351.1 0.0 -1.4 0.0
3 1 -2352.3 -214.9 -9.4 6.7
3 2 1225.6 245.0 7.2 -3.1
3 3 581.9 -538.3 -2.6 -1.1
4 0 907.3 0.0 0.8 0.0
4 1 813.8 283.9 -0.4 0.6
4 2 120.3 -188.1 -3.1 0.1
4 3 -335.0 180.9 0.8 1.7
4 4 70.3 -329.5 -1.8 2.2
5 0 -232.6 0.0 -0.3 0.0
5 1 360.1 47.0 0.1 0.4
5 2 192.4 196.9 -0.5 -0.6
5 3 -141.1 -119.0 0.1 0.5
5 4 -157.4 16.1 0.6 0.6
5 5 13.7 93.8 0.0 0.0
6 0 67.4 0.0 0.1 0.0
6 1 65.9 -20.6 -0.3 0.1
6 2 73.3 23.6 -0.1 -0.2
6 3 -121.5 54.3 0.0 0.1
6 4 -36.2 -64.4 -0.1 0.0
6 5 13.5 9.0 0.0 0.0
6 6 -64.7 68.0 0.0 0.0
7 0 80.6 0.0 0.0 0.0
7 1 -76.8 -51.4 0.0 0.1
7 2 -8.3 -16.8 0.0 0.0
7 3 56.5 2.3 0.0 0.0
7 4 15.8 23.5 0.0 0.0
7 5 6.4 -2.2 0.0 0.0
7 6 -7.2 8.6 0.0 0.0
7 7 9.8 -3.9 0.0 0.0
8 0 23.6 0.0 0.0 0.0
8 1 9.8 8.4 0.0 0.0
8 2 -17.5 -15.3 0.0 0.0
8 3 -0.4 12.8 0.0 0.0
8 4 -21.1 -7.4 0.0 0.0
8 5 15.3 9.3 0.0 0.0
8 6 13.7 6.7 0.0 0.0
8 7 -16.5 -0.3 0.0 0.0
8 8 -0.3 3.0 0.0 0.0
9 0 5.0 0.0 0.0 0.0
9 1 8.2 -23.3 0.0 0.0
9 2 2.9 11.1 0.0 0.0
9 3 -1.4 9.8 0.0 0.0
9 4 -1.1 -6.4 0.0 0.0
9 5 -13.3 1.5 0.0 0.0
9 6 1.1 4.2 0.0 0.0
9 7 8.9 0.1 0.0 0.0
9 8 -9.3 -1.4 0.0 0.0
9 9 -11.9 9.9 0.0 0.0
10 0 -1.9 0.0 0.0 0.0
10 1 -6.2 3.4 0.0 0.0
10 2 0.3 -0.4 0.0 0.0
10 3 0.6 -0.8 0.0 0.0
10 4 1.7 -0.7 0.0 0.0
10 5 -0.5 0.3 0.0 0.0
10 6 0.2 -1.7 0.0 0.0
10 7 1.7 1.3 0.0 0.0
10 8 -0.2 0.2 0.0 0.0
10 9 0.3 0.4 0.0 0.0
10 10 -1.2 0.0 0.0 0.0
11 0 3.1 0.0 0.0 0.0
11 1 -1.5 -0.9 0.0 0.0
11 2 -2.3 -2.2 0.0 0.0
11 3 2.1 -0.3 0.0 0.0
11 4 -0.9 0.3 0.0 0.0
11 5 0.6 0.9 0.0 0.0
11 6 0.5 0.2 0.0 0.0
11 7 -0.4 0.1 0.0 0.0
11 8 0.1 0.5 0.0 0.0
11 9 -0.1 0.4 0.0 0.0
11 10 -0.3 -0.2 0.0 0.0
11 11 0.2 -0.2 0.0 0.0
12 0 -2.0 0.0 0.0 0.0
12 1 -0.1 0.6 0.0 0.0
12 2 0.5 -0.6 0.0 0.0
12 3 1.3 0.1 0.0 0.0
12 4 -0.5 0.3 0.0 0.0
12 5 0.4 -0.1 0.0 0.0
12 6 0.1 0.1 0.0 0.0
12 7 0.0 -0.1 0.0 0.0
12 8 0.2 0.2 0.0 0.0
12 9 -0.3 0.0 0.0 0.0
12 10 -0.1 0.0 0.0 0.0
12 11 0.0 0.0 0.0 0.0
12 12 0.1 0.0 0.0 0.0
999999999999999999999999999999999999999999999999
999999999999999999999999999999999999999999999999
@@ -0,0 +1,93 @@
2015.0 WMM-2015 12/10/2014
1 0 -29404.8 0.0 6.7 0.0
1 1 -1450.9 4652.5 7.7 -25.9
2 0 -2500.0 0.0 -11.5 0.0
2 1 2982.0 -2991.6 -7.1 -30.2
2 2 1677.0 -734.8 -2.2 -23.9
3 0 1363.9 0.0 2.8 0.0
3 1 -2381.0 -82.2 -6.2 5.2
3 2 1236.2 241.8 3.4 -1.0
3 3 525.7 -542.9 -12.2 1.1
4 0 903.1 0.0 -1.1 0.0
4 1 809.4 282.0 -1.6 0.2
4 2 86.2 -158.4 -6.0 6.9
4 3 -309.4 199.8 5.4 3.7
4 4 47.9 -350.1 -5.5 8.6
5 0 -234.4 0.0 -0.5 0.0
5 1 363.1 47.7 0.5 1.0
5 2 187.8 208.4 -0.7 -0.8
5 3 -140.7 -121.3 0.8 1.0
5 4 -151.2 32.3 1.2 3.0
5 5 13.7 99.1 1.0 0.5
6 0 65.9 0.0 -0.6 0.0
6 1 65.6 -19.1 -0.4 0.1
6 2 73.0 25.0 -0.2 -0.4
6 3 -121.5 52.7 0.4 1.2
6 4 -36.2 -64.4 -0.7 0.6
6 5 13.5 9.0 0.1 0.1
6 6 -64.7 68.0 0.8 -0.1
7 0 80.6 0.0 -0.1 0.0
7 1 -76.8 -51.4 -0.3 0.5
7 2 -8.3 -16.8 0.1 0.1
7 3 56.5 2.3 0.4 -0.7
7 4 15.8 23.5 0.0 -0.5
7 5 6.4 -2.2 -0.3 0.3
7 6 -7.2 8.6 0.0 0.0
7 7 9.8 -3.9 0.3 0.2
8 0 23.6 0.0 -0.3 0.0
8 1 9.8 8.4 -0.1 0.1
8 2 -17.5 -15.3 0.3 0.1
8 3 -0.4 12.8 0.1 -0.3
8 4 -21.1 -7.4 0.0 0.0
8 5 15.3 9.3 -0.1 0.2
8 6 13.7 6.7 0.1 0.0
8 7 -16.5 -0.3 0.2 0.0
8 8 -0.3 3.0 0.0 0.0
9 0 5.0 0.0 0.0 0.0
9 1 8.2 -23.3 -0.1 -0.2
9 2 2.9 11.1 -0.1 -0.1
9 3 -1.4 9.8 0.1 0.2
9 4 -1.1 -6.4 0.0 0.1
9 5 -13.3 1.5 0.3 0.0
9 6 1.1 4.2 0.0 0.0
9 7 8.9 0.1 -0.1 0.0
9 8 -9.3 -1.4 -0.1 0.0
9 9 -11.9 9.9 0.1 0.0
10 0 -1.9 0.0 0.0 0.0
10 1 -6.2 3.4 0.0 0.0
10 2 0.3 -0.4 0.0 0.0
10 3 0.6 -0.8 0.0 0.0
10 4 1.7 -0.7 0.0 0.0
10 5 -0.5 0.3 0.0 0.0
10 6 0.2 -1.7 0.0 0.0
10 7 1.7 1.3 0.0 0.0
10 8 -0.2 0.2 0.0 0.0
10 9 0.3 0.4 0.0 0.0
10 10 -1.2 0.0 0.0 0.0
11 0 3.1 0.0 0.0 0.0
11 1 -1.5 -0.9 0.0 0.0
11 2 -2.3 -2.2 0.0 0.0
11 3 2.1 -0.3 0.0 0.0
11 4 -0.9 0.3 0.0 0.0
11 5 0.6 0.9 0.0 0.0
11 6 0.5 0.2 0.0 0.0
11 7 -0.4 0.1 0.0 0.0
11 8 0.1 0.5 0.0 0.0
11 9 -0.1 0.4 0.0 0.0
11 10 -0.3 -0.2 0.0 0.0
11 11 0.2 -0.2 0.0 0.0
12 0 -2.0 0.0 0.0 0.0
12 1 -0.1 0.6 0.0 0.0
12 2 0.5 -0.6 0.0 0.0
12 3 1.3 0.1 0.0 0.0
12 4 -0.5 0.3 0.0 0.0
12 5 0.4 -0.1 0.0 0.0
12 6 0.1 0.1 0.0 0.0
12 7 0.0 -0.1 0.0 0.0
12 8 0.2 0.2 0.0 0.0
12 9 -0.3 0.0 0.0 0.0
12 10 -0.1 0.0 0.0 0.0
12 11 0.0 0.0 0.0 0.0
12 12 0.1 0.0 0.0 0.0
999999999999999999999999999999999999999999999999
999999999999999999999999999999999999999999999999
@@ -0,0 +1,93 @@
2020.0 WMM-2020 12/10/2019
1 0 -29404.5 0.0 6.7 0.0
1 1 -1450.7 4652.9 7.7 -25.1
2 0 -2500.0 0.0 -11.5 0.0
2 1 2982.0 -2991.6 -7.1 -30.2
2 2 1676.8 -734.8 -2.2 -23.9
3 0 1363.9 0.0 2.8 0.0
3 1 -2381.0 -82.2 -6.2 5.7
3 2 1236.2 241.8 3.4 -1.0
3 3 525.7 -542.9 -12.2 1.1
4 0 903.1 0.0 -1.1 0.0
4 1 809.4 282.0 -1.6 0.2
4 2 86.2 -158.4 -6.0 6.9
4 3 -309.4 199.8 5.4 3.7
4 4 47.9 -350.1 -5.5 -5.6
5 0 -234.4 0.0 -0.3 0.0
5 1 363.1 47.7 0.6 0.1
5 2 187.8 208.4 -0.7 2.5
5 3 -140.7 -121.3 0.1 -0.9
5 4 -151.2 32.2 1.2 3.0
5 5 13.7 99.1 1.0 0.5
6 0 65.9 0.0 -0.6 0.0
6 1 65.6 -19.1 -0.4 0.1
6 2 73.0 25.0 0.5 -1.8
6 3 -121.5 52.7 1.4 -1.4
6 4 -36.2 -64.4 -1.4 0.9
6 5 13.5 9.0 -0.0 0.1
6 6 -64.7 68.1 0.8 1.0
7 0 80.6 0.0 -0.1 0.0
7 1 -76.8 -51.4 -0.3 0.5
7 2 -8.3 -16.8 -0.1 0.6
7 3 56.5 2.3 0.7 -0.7
7 4 15.8 23.5 0.2 -0.2
7 5 6.4 -2.2 -0.5 -1.2
7 6 -7.2 -27.2 -0.8 0.2
7 7 9.8 -1.9 1.0 0.3
8 0 23.6 0.0 -0.1 0.0
8 1 9.8 8.4 0.1 -0.3
8 2 -17.5 -15.3 -0.1 0.7
8 3 -0.4 12.8 0.5 -0.2
8 4 -21.1 -11.8 -0.1 0.5
8 5 15.3 14.9 0.4 -0.3
8 6 13.7 3.6 0.5 -0.5
8 7 -16.5 -6.9 0.0 0.4
8 8 -0.3 2.8 0.4 0.1
9 0 5.0 0.0 -0.1 0.0
9 1 8.2 -23.3 -0.2 -0.3
9 2 2.9 11.1 -0.0 0.2
9 3 -1.4 9.8 0.4 -0.4
9 4 -1.1 -5.1 -0.3 0.4
9 5 -13.3 -6.2 -0.0 0.1
9 6 1.1 7.8 0.3 -0.0
9 7 8.9 0.4 -0.0 -0.2
9 8 -9.3 -1.5 -0.0 0.5
9 9 -11.9 9.7 -0.4 0.2
10 0 -1.9 0.0 0.0 0.0
10 1 -6.2 3.4 -0.0 -0.0
10 2 -0.1 -0.2 -0.0 0.1
10 3 1.7 3.5 0.2 -0.3
10 4 -0.9 4.8 -0.1 0.1
10 5 0.6 -8.6 -0.2 -0.2
10 6 -0.9 -0.1 -0.0 0.1
10 7 1.9 -4.2 -0.1 -0.0
10 8 1.4 -3.4 -0.2 -0.1
10 9 -2.4 -0.1 -0.1 0.2
10 10 -3.9 -8.8 -0.0 -0.0
11 0 3.0 0.0 -0.0 0.0
11 1 -1.4 -0.0 -0.1 -0.0
11 2 -2.5 2.6 -0.0 0.1
11 3 2.4 -0.5 0.0 0.0
11 4 -0.9 -0.4 -0.0 0.2
11 5 0.3 0.6 -0.1 -0.0
11 6 -0.7 -0.2 0.0 0.0
11 7 -0.1 -1.7 -0.0 0.1
11 8 1.4 -1.6 -0.1 -0.0
11 9 -0.6 -3.0 -0.1 -0.1
11 10 0.2 -2.0 -0.1 0.0
11 11 3.1 -2.6 -0.1 -0.0
12 0 -2.0 0.0 0.0 0.0
12 1 -0.1 -1.2 -0.0 -0.0
12 2 0.5 0.5 -0.0 0.0
12 3 1.3 1.3 0.0 -0.1
12 4 -1.2 -1.8 -0.0 0.1
12 5 0.7 0.1 -0.0 -0.0
12 6 0.3 0.7 0.0 0.0
12 7 0.5 -0.1 -0.0 -0.0
12 8 -0.2 0.6 0.0 0.1
12 9 -0.5 0.2 -0.0 -0.0
12 10 0.1 -0.9 -0.0 -0.0
12 11 -1.1 -0.0 -0.0 0.0
12 12 -0.3 0.5 -0.1 -0.1
999999999999999999999999999999999999999999999999
999999999999999999999999999999999999999999999999
@@ -0,0 +1,93 @@
2025.0 WMM-2025 11/13/2024
1 0 -29351.8 0.0 12.0 0.0
1 1 -1410.8 4545.4 9.7 -21.5
2 0 -2556.6 0.0 -11.6 0.0
2 1 2951.1 -3133.6 -5.2 -27.7
2 2 1649.3 -815.1 -8.0 -12.1
3 0 1361.0 0.0 -1.3 0.0
3 1 -2404.1 -56.6 -4.2 4.0
3 2 1243.8 237.5 0.4 -0.3
3 3 453.6 -549.5 -15.6 -4.1
4 0 895.0 0.0 -1.6 0.0
4 1 799.5 278.6 -2.4 -1.1
4 2 55.7 -133.9 -6.0 4.1
4 3 -281.1 212.0 5.6 1.6
4 4 12.1 -375.6 -7.0 -4.4
5 0 -233.2 0.0 0.6 0.0
5 1 368.9 45.4 1.4 -0.5
5 2 187.2 220.2 0.0 2.2
5 3 -138.7 -122.9 0.6 0.4
5 4 -142.0 43.0 2.2 1.7
5 5 20.9 106.1 0.9 1.9
6 0 64.4 0.0 -0.2 0.0
6 1 63.8 -18.4 -0.4 0.3
6 2 76.9 16.8 0.9 -1.6
6 3 -115.7 48.8 1.2 -0.4
6 4 -40.9 -59.8 -0.9 0.9
6 5 14.9 10.9 0.3 0.7
6 6 -60.7 72.7 0.9 0.9
7 0 79.5 0.0 -0.0 0.0
7 1 -77.0 -48.9 -0.1 0.6
7 2 -8.8 -14.4 -0.1 0.5
7 3 59.3 -1.0 0.5 -0.8
7 4 15.8 23.4 -0.1 0.0
7 5 2.5 -7.4 -0.8 -1.0
7 6 -11.1 -25.1 -0.8 0.6
7 7 14.2 -2.3 0.8 -0.2
8 0 23.2 0.0 -0.1 0.0
8 1 10.8 7.1 0.2 -0.2
8 2 -17.5 -12.6 0.0 0.5
8 3 2.0 11.4 0.5 -0.4
8 4 -21.7 -9.7 -0.1 0.4
8 5 16.9 12.7 0.3 -0.5
8 6 15.0 0.7 0.2 -0.6
8 7 -16.8 -5.2 -0.0 0.3
8 8 0.9 3.9 0.2 0.2
9 0 4.6 0.0 -0.0 0.0
9 1 7.8 -24.8 -0.1 -0.3
9 2 3.0 12.2 0.1 0.3
9 3 -0.2 8.3 0.3 -0.3
9 4 -2.5 -3.3 -0.3 0.3
9 5 -13.1 -5.2 0.0 0.2
9 6 2.4 7.2 0.3 -0.1
9 7 8.6 -0.6 -0.1 -0.2
9 8 -8.7 0.8 0.1 0.4
9 9 -12.9 10.0 -0.1 0.1
10 0 -1.3 0.0 0.1 0.0
10 1 -6.4 3.3 0.0 0.0
10 2 0.2 0.0 0.1 -0.0
10 3 2.0 2.4 0.1 -0.2
10 4 -1.0 5.3 -0.0 0.1
10 5 -0.6 -9.1 -0.3 -0.1
10 6 -0.9 0.4 0.0 0.1
10 7 1.5 -4.2 -0.1 0.0
10 8 0.9 -3.8 -0.1 -0.1
10 9 -2.7 0.9 -0.0 0.2
10 10 -3.9 -9.1 -0.0 -0.0
11 0 2.9 0.0 0.0 0.0
11 1 -1.5 0.0 -0.0 -0.0
11 2 -2.5 2.9 0.0 0.1
11 3 2.4 -0.6 0.0 -0.0
11 4 -0.6 0.2 0.0 0.1
11 5 -0.1 0.5 -0.1 -0.0
11 6 -0.6 -0.3 0.0 -0.0
11 7 -0.1 -1.2 -0.0 0.1
11 8 1.1 -1.7 -0.1 -0.0
11 9 -1.0 -2.9 -0.1 0.0
11 10 -0.2 -1.8 -0.1 0.0
11 11 2.6 -2.3 -0.1 0.0
12 0 -2.0 0.0 0.0 0.0
12 1 -0.2 -1.3 0.0 -0.0
12 2 0.3 0.7 -0.0 0.0
12 3 1.2 1.0 -0.0 -0.1
12 4 -1.3 -1.4 -0.0 0.1
12 5 0.6 -0.0 -0.0 -0.0
12 6 0.6 0.6 0.1 -0.0
12 7 0.5 -0.1 -0.0 -0.0
12 8 -0.1 0.8 0.0 0.0
12 9 -0.4 0.1 0.0 -0.0
12 10 -0.2 -1.0 -0.1 -0.0
12 11 -1.3 0.1 -0.0 0.0
12 12 -0.7 0.2 -0.1 -0.1
999999999999999999999999999999999999999999999999
999999999999999999999999999999999999999999999999
File diff suppressed because it is too large Load Diff
@@ -3,12 +3,10 @@ package com.sap.sailing.declination;
import java.io.IOException;
import java.text.ParseException;
import com.sap.sailing.declination.impl.BGSImporter;
import com.sap.sailing.declination.impl.DeclinationImporter;
import com.sap.sailing.declination.impl.DeclinationServiceImpl;
import com.sap.sailing.declination.impl.WMMCalculatorDeclinationService;
import com.sap.sailing.domain.common.Mile;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.CentralAngleDistance;
import com.sap.sse.common.Distance;
import com.sap.sse.common.TimePoint;
@@ -17,7 +15,7 @@ public interface DeclinationService {
* A default implementation with a spatial default precision of 60 {@link Mile#METERS_PER_GEOGRAPHICAL_MILE
* nautical miles} which equals the length of an arc with one degree on a meridian.
*/
DeclinationService INSTANCE = new DeclinationServiceImpl(new CentralAngleDistance(1./180.*Math.PI), new BGSImporter());
DeclinationService INSTANCE = new WMMCalculatorDeclinationService();
/**
* Obtains declination information with the default precision of this declination service in time and space
@@ -0,0 +1,39 @@
package com.sap.sailing.declination.impl;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.domain.common.Position;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.TimePoint;
public abstract class AbstractDeclinationRecord implements Declination {
private static final long serialVersionUID = 2701783831406402231L;
private final Position position;
private final TimePoint timePoint;
private final Bearing bearing;
public AbstractDeclinationRecord(Position position, TimePoint timePoint, Bearing bearing) {
this.position = position;
this.timePoint = timePoint;
this.bearing = bearing;
}
@Override
public Position getPosition() {
return position;
}
@Override
public TimePoint getTimePoint() {
return timePoint;
}
@Override
public Bearing getBearing() {
return bearing;
}
@Override
public String toString() {
return ""+getTimePoint()+"@"+getPosition()+": "+getBearing();
}
}
@@ -67,7 +67,8 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
public class BGSImporter extends DeclinationImporter {
// private static final String URL_PATTERN = "http://geomag.bgs.ac.uk/web_service/GMModels/bggm/2015/?latitude=%f&longitude=%f&altitude=0&date=%d-%d-%d&format=xml";
// private static final String URL_PATTERN = "http://geomag.bgs.ac.uk/web_service/GMModels/wmm/2020/?latitude=%f&longitude=%f&altitude=0&date=%d-%d-%d&format=xml";
private static final String URL_PATTERN = "http://geomag.bgs.ac.uk/web_service/GMModels/igrf/13/?latitude=%f&longitude=%f&altitude=0&date=%d-%d-%d&format=xml";
private static final String URL_PATTERN_PRE_2025 = "http://geomag.bgs.ac.uk/web_service/GMModels/igrf/13/?latitude=%f&longitude=%f&altitude=0&date=%d-%d-%d&format=xml";
private static final String URL_PATTERN_POST_2025 = "https://geomag.bgs.ac.uk/web_service/GMModels/wmm/2025?latitude=%f&longitude=%f&altitude=0&date=%d-%d-%d&format=xml";
private static class XmlElementHandler extends DefaultHandler {
private Date dateAsDecimalYear;
@@ -156,7 +157,8 @@ public class BGSImporter extends DeclinationImporter {
throws IOException, ParserConfigurationException, SAXException {
final Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
cal.setTime(timePoint.asDate());
final URL url = new URL(String.format(URL_PATTERN, position.getLatDeg(), position.getLngDeg(), cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH)));
final int year = cal.get(Calendar.YEAR);
final URL url = new URL(String.format(year >= 2025 ? URL_PATTERN_POST_2025 : URL_PATTERN_PRE_2025, position.getLatDeg(), position.getLngDeg(), year, cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH)));
return getDeclinationFromXml(url.openStream());
}
}
@@ -0,0 +1,41 @@
package com.sap.sailing.declination.impl;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.domain.common.Position;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
/**
* A declination record that does not know about any annual change because it has been computed precisely for
* the time point and position requested. Therefore, no {@link #getBearingCorrectedTo(TimePoint) correction} to
* any time point other than the one {@link #getTimePoint() requested} can be performed. An exception will
* be thrown if that happens.
*
* @author Axel Uhl (d043530)
*
*/
public class DeclinationRecordForExactTimePoint extends AbstractDeclinationRecord implements Declination {
private static final long serialVersionUID = -94512743120385233L;
public DeclinationRecordForExactTimePoint(Position position, TimePoint timePoint, Bearing bearing) {
super(position, timePoint, bearing);
}
@Override
public Bearing getAnnualChange() {
return null;
}
@Override
public Bearing getBearingCorrectedTo(TimePoint timePoint) {
if (Util.equalsWithNull(timePoint, getTimePoint())) {
return getBearing();
} else {
throw new IllegalArgumentException("Declination computed precisely for " + getTimePoint()
+ " cannot be corrected to any other time point " + timePoint
+ " because we lack knowledge of the annual change here.");
}
}
}
@@ -6,35 +6,15 @@ import com.sap.sse.common.Bearing;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.DegreeBearingImpl;
public class DeclinationRecordImpl implements Declination {
public class DeclinationRecordImpl extends AbstractDeclinationRecord implements Declination {
private static final long serialVersionUID = 6918630656182340186L;
private final Position position;
private final TimePoint timePoint;
private final Bearing bearing;
private final Bearing annualChange;
public DeclinationRecordImpl(Position position, TimePoint timePoint, Bearing bearing, Bearing annualChange) {
super();
this.position = position;
this.timePoint = timePoint;
this.bearing = bearing;
super(position, timePoint, bearing);
this.annualChange = annualChange;
}
@Override
public Position getPosition() {
return position;
}
@Override
public TimePoint getTimePoint() {
return timePoint;
}
@Override
public Bearing getBearing() {
return bearing;
}
@Override
public Bearing getAnnualChange() {
return annualChange;
@@ -50,5 +30,4 @@ public class DeclinationRecordImpl implements Declination {
public String toString() {
return ""+getTimePoint()+"@"+getPosition()+": "+getBearing()+", "+getAnnualChange()+"/year";
}
}
@@ -14,7 +14,7 @@ import com.sap.sailing.domain.common.quadtree.QuadTree;
import com.sap.sse.common.Distance;
import com.sap.sse.common.TimePoint;
public class DeclinationServiceImpl implements DeclinationService {
public class DeclinationServiceImplWithStore implements DeclinationService {
private final Distance defaultMaxDistance;
private final DeclinationStore persistentStore;
private final DeclinationImporter declinationImporter;
@@ -29,7 +29,7 @@ public class DeclinationServiceImpl implements DeclinationService {
*/
private final Map<Integer, QuadTree<Declination>> importerCache;
public DeclinationServiceImpl(Distance defaultMaxDistance, DeclinationImporter declinationImporter) {
public DeclinationServiceImplWithStore(Distance defaultMaxDistance, DeclinationImporter declinationImporter) {
this.declinationImporter = declinationImporter;
this.defaultMaxDistance = defaultMaxDistance;
this.yearStore = new HashMap<Integer, QuadTree<Declination>>();
@@ -207,7 +207,7 @@ public class DeclinationStore {
existingDeclinationRecord = storedDeclinations.get(point);
}
if (existingDeclinationRecord == null
|| DeclinationServiceImpl.timeAndSpaceDistance(existingDeclinationRecord.getPosition().getDistance(point),
|| DeclinationServiceImplWithStore.timeAndSpaceDistance(existingDeclinationRecord.getPosition().getDistance(point),
timePoint, existingDeclinationRecord.getTimePoint()) > 0.1) {
// less than ~6 nautical miles and/or ~.6 months off
fetchAndAppendDeclination(timePoint, point, importer, out);
@@ -0,0 +1,389 @@
package com.sap.sailing.declination.impl;
import java.io.BufferedReader;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/* License Statement from the NOAA
* The WMM source code is in the public domain and not licensed or
* under copyright. The information and software may be used freely
* by the public. As required by 17 U.S.C. 403, third parties producing
* copyrighted works consisting predominantly of the material produced
* by U.S. government agencies must provide notice with such work(s)
* identifying the U.S. Government material incorporated and stating
* that such material is not subject to copyright protection.*/
import java.util.GregorianCalendar;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Named;
import com.sap.sse.common.TimePoint;
/**
* <p>
* Class to calculate magnetic declination, magnetic field strength, inclination etc. for any point on the earth.
* </p>
* <p>
* Adapted from the geomagc software and World Magnetic Model of the NOAA Satellite and Information Service, National
* Geophysical Data Center. Caching removed to make stateless, except for the reading of the coefficients file.
* Results are now returned as instances of the inner class {@link Result}.
* </p>
* http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml
* <p>
* © Deep Pradhan, 2017
* </p>
*/
class Geomagnetism implements Named {
private static final long serialVersionUID = -2814152697634383730L;
class Result {
/** Geomagnetic declination (decimal degrees) [opposite of variation, positive Eastward/negative Westward] */
private final double declination;
/** Geomagnetic inclination/dip angle (degrees) [positive downward] */
private final double inclination;
/** Geomagnetic field intensity/strength (nano Teslas) */
private final double intensity;
/** Geomagnetic horizontal field intensity/strength (nano Teslas) */
private final double bh;
/** Geomagnetic vertical field intensity/strength (nano Teslas) [positive downward] */
private final double bz;
/** Geomagnetic North South (northerly component) field intensity/strength (nano Tesla) */
private final double bx;
/** Geomagnetic East West (easterly component) field intensity/strength (nano Teslas) */
private final double by;
public Result(double declination, double inclination, double intensity, double bh, double bz, double bx, double by) {
this.declination = declination;
this.inclination = inclination;
this.intensity = intensity;
this.bh = bh;
this.bz = bz;
this.bx = bx;
this.by = by;
}
/** @return Geomagnetic declination (degrees) [opposite of variation, positive Eastward/negative Westward] */
double getDeclination() {
return declination;
}
/** @return Geomagnetic inclination/dip angle (degrees) [positive downward] */
double getInclination() {
return inclination;
}
/** @return Geomagnetic field intensity/strength (nano Teslas) */
double getIntensity() {
return intensity;
}
/** @return Geomagnetic horizontal field intensity/strength (nano Teslas) */
double getHorizontalIntensity() {
return bh;
}
/** @return Geomagnetic vertical field intensity/strength (nano Teslas) [positive downward] */
double getVerticalIntensity() {
return bz;
}
/** @return Geomagnetic North South (northerly component) field intensity/strength (nano Tesla) */
double getNorthIntensity() {
return bx;
}
/** @return Geomagnetic East West (easterly component) field intensity/strength (nano Teslas) */
double getEastIntensity() {
return by;
}
String getModelName() {
return getName();
}
TimePoint getModelIssueTimePoint() {
return getIssueTimePoint();
}
}
private final static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM/dd/yyyy");
private final TimePoint startOfValidity;
/**
* Initialise the instance without calculations
*
* @param cofReader
* a reader for a file in .COF format, such as WMM2025.COF; expects to find trailing lines with at least
* "999999" in them
*/
Geomagnetism(BufferedReader cofReader) throws IOException, ParseException {
// Initialize constants
maxord = MAX_DEG;
sp[0] = 0;
cp[0] = snorm[0] = pp[0] = 1;
dp[0][0] = 0;
c[0][0] = 0;
cd[0][0] = 0;
final String headerLine = cofReader.readLine();
final String[] headerFields = headerLine.trim().split("\\s+");
epoch = Double.parseDouble(headerFields[0]);
startOfValidity = TimePoint.of(new GregorianCalendar((int) epoch, 1, 1, 0, 0, 0).getTimeInMillis()).plus(Duration.ONE_HOUR.times(365.0*24.0*(epoch-((int) epoch))));
name = headerFields[1];
issueTimePoint = TimePoint.of(simpleDateFormat.parse(headerFields[2]));
String[] tokens;
double gnm, hnm, dgnm, dhnm;
String line;
while (!(line=cofReader.readLine()).startsWith("999999")) {
tokens = line.trim().split("\\s+");
final int n = Integer.parseInt(tokens[0]);
final int m = Integer.parseInt(tokens[1]);
gnm = Double.parseDouble(tokens[2]);
hnm = Double.parseDouble(tokens[3]);
dgnm = Double.parseDouble(tokens[4]);
dhnm = Double.parseDouble(tokens[5]);
if (m <= n) {
c[m][n] = gnm;
cd[m][n] = dgnm;
if (m != 0) {
c[n][m - 1] = hnm;
cd[n][m - 1] = dhnm;
}
}
}
// Convert schmidt normalized gauss coefficients to unnormalized
snorm[0] = 1;
double flnmj;
for (int j, n = 1; n <= maxord; n++) {
snorm[n] = snorm[n - 1] * (2 * n - 1) / n;
j = 2;
for (int m = 0, d1 = 1, d2 = (n - m + d1) / d1; d2 > 0; d2--, m += d1) {
k[m][n] = (double) (((n - 1) * (n - 1)) - (m * m)) / (double) ((2 * n - 1) * (2 * n - 3));
if (m > 0) {
flnmj = ((n - m + 1) * j) / (double) (n + m);
snorm[n + m * 13] = snorm[n + (m - 1) * 13] * Math.sqrt(flnmj);
j = 1;
c[n][m - 1] = snorm[n + m * 13] * c[n][m - 1];
cd[n][m - 1] = snorm[n + m * 13] * cd[n][m - 1];
}
c[m][n] = snorm[n + m * 13] * c[m][n];
cd[m][n] = snorm[n + m * 13] * cd[m][n];
}
fn[n] = (n + 1);
fm[n] = n;
}
k[1][1] = 0;
fm[0] = 0;
}
/**
* Calculate for given location, altitude and date
*
* @param longitude
* Longitude in decimal degrees
* @param latitude
* Latitude in decimal degrees
* @param altitude
* Altitude in metres (with respect to WGS-1984 ellipsoid)
* @param calendar
* Calendar for date of calculation
*/
Result calculate(double longitude, double latitude, double altitude, GregorianCalendar calendar) {
double rlon = Math.toRadians(longitude), rlat = Math.toRadians(latitude),
altitudeKm = Double.isNaN(altitude) ? 0 : altitude / 1000,
yearFraction = calendar.get(GregorianCalendar.YEAR)
+ (double) calendar.get(GregorianCalendar.DAY_OF_YEAR)
/ calendar.getActualMaximum(GregorianCalendar.DAY_OF_YEAR),
dt = yearFraction - epoch, srlon = Math.sin(rlon), srlat = Math.sin(rlat), crlon = Math.cos(rlon),
crlat = Math.cos(rlat), srlat2 = srlat * srlat, crlat2 = crlat * crlat, a2 = WGS84_A * WGS84_A,
b2 = WGS84_B * WGS84_B, c2 = a2 - b2, a4 = a2 * a2, b4 = b2 * b2, c4 = a4 - b4;
sp[1] = srlon;
cp[1] = crlon;
// Convert from geodetic coords. to spherical coords.
double q = Math.sqrt(a2 - c2 * srlat2), q1 = altitudeKm * q,
q2 = ((q1 + a2) / (q1 + b2)) * ((q1 + a2) / (q1 + b2)),
r2 = ((altitudeKm * altitudeKm) + 2 * q1 + (a4 - c4 * srlat2) / (q * q));
ct = srlat / Math.sqrt(q2 * crlat2 + srlat2);
st = Math.sqrt(1 - (ct * ct));
r = Math.sqrt(r2);
d = Math.sqrt(a2 * crlat2 + b2 * srlat2);
ca = (altitudeKm + d) / r;
sa = c2 * crlat * srlat / (r * d);
for (int m = 2; m <= maxord; m++) {
sp[m] = sp[1] * cp[m - 1] + cp[1] * sp[m - 1];
cp[m] = cp[1] * cp[m - 1] - sp[1] * sp[m - 1];
}
double aor = IAU66_RADIUS / r, ar = aor * aor, br = 0, bt = 0, bp = 0, bpp = 0, par, parp, temp1, temp2;
for (int n = 1; n <= maxord; n++) {
ar = ar * aor;
for (int m = 0, d3 = 1, d4 = (n + m + d3) / d3; d4 > 0; d4--, m += d3) {
// Compute unnormalized associated legendre polynomials and derivatives via recursion relations
if (n == m) {
snorm[n + m * 13] = st * snorm[n - 1 + (m - 1) * 13];
dp[m][n] = st * dp[m - 1][n - 1] + ct * snorm[n - 1 + (m - 1) * 13];
}
if (n == 1 && m == 0) {
snorm[n + m * 13] = ct * snorm[n - 1 + m * 13];
dp[m][n] = ct * dp[m][n - 1] - st * snorm[n - 1 + m * 13];
}
if (n > 1 && n != m) {
if (m > n - 2) {
snorm[n - 2 + m * 13] = 0;
}
if (m > n - 2) {
dp[m][n - 2] = 0;
}
snorm[n + m * 13] = ct * snorm[n - 1 + m * 13] - k[m][n] * snorm[n - 2 + m * 13];
dp[m][n] = ct * dp[m][n - 1] - st * snorm[n - 1 + m * 13] - k[m][n] * dp[m][n - 2];
}
// Time adjust the gauss coefficients
tc[m][n] = c[m][n] + dt * cd[m][n];
if (m != 0) {
tc[n][m - 1] = c[n][m - 1] + dt * cd[n][m - 1];
}
// Accumulate terms of the spherical harmonic expansions
par = ar * snorm[n + m * 13];
if (m == 0) {
temp1 = tc[m][n] * cp[m];
temp2 = tc[m][n] * sp[m];
} else {
temp1 = tc[m][n] * cp[m] + tc[n][m - 1] * sp[m];
temp2 = tc[m][n] * sp[m] - tc[n][m - 1] * cp[m];
}
bt = bt - ar * temp1 * dp[m][n];
bp += (fm[m] * temp2 * par);
br += (fn[n] * temp1 * par);
// Special case: north/south geographic poles
if (st == 0 && m == 1) {
if (n == 1) {
pp[n] = pp[n - 1];
} else {
pp[n] = ct * pp[n - 1] - k[m][n] * pp[n - 2];
}
parp = ar * pp[n];
bpp += (fm[m] * temp2 * parp);
}
}
}
if (st == 0) {
bp = bpp;
} else {
bp /= st;
}
// Rotate magnetic vector components from spherical to geodetic coordinates
// bx must be the east-west field component
// by must be the north-south field component
// bz must be the vertical field component.
final double bx = -bt * ca - br * sa;
final double by = bp;
final double bz = bt * sa - br * ca;
// Compute declination (dec), inclination (dip) and total intensity (ti)
final double bh = Math.sqrt((bx * bx) + (by * by));
final double intensity = Math.sqrt((bh * bh) + (bz * bz));
// Calculate the declination.
final double declination = Math.toDegrees(Math.atan2(by, bx));
final double inclination = Math.toDegrees(Math.atan2(bz, bh));
return new Result(declination, inclination, intensity, bh, bz, bx, by);
}
/**
* Calculate for given location, altitude and current date
*
* @param longitude
* Longitude in decimal degrees
* @param latitude
* Latitude in decimal degrees
* @param altitude
* Altitude in metres (with respect to WGS-1984 ellipsoid)
*/
Result calculate(double longitude, double latitude, double altitude) {
return calculate(longitude, latitude, altitude, new GregorianCalendar());
}
/**
* Calculate for given location, zero altitude and current date
*
* @param longitude
* Longitude in decimal degrees
* @param latitude
* Latitude in decimal degrees
*/
Result calculate(double longitude, double latitude) {
return calculate(longitude, latitude, 0);
}
/** @return The date in years, for the start of the valid time of the fit coefficients */
public double getEpoch() {
return epoch;
}
public TimePoint getStartOfValidity() {
return startOfValidity;
}
public String getName() {
return name;
}
public TimePoint getIssueTimePoint() {
return issueTimePoint;
}
private final String name;
private final TimePoint issueTimePoint;
/** Mean radius of IAU-66 ellipsoid, in km */
private static final double IAU66_RADIUS = 6371.2;
/** Semi-major axis of WGS-1984 ellipsoid, in km */
private static final double WGS84_A = 6378.137;
/** Semi-minor axis of WGS-1984 ellipsoid, in km */
private static final double WGS84_B = 6356.7523142;
/** The maximum number of degrees of the spherical harmonic model */
private static final int MAX_DEG = 12;
/** The Gauss coefficients of main geomagnetic model (nt) */
private double c[][] = new double[300][300];
/** The Gauss coefficients of secular geomagnetic model (nt/yr) */
private double cd[][] = new double[300][300];
/** The time adjusted geomagnetic gauss coefficients (nt) */
private double tc[][] = new double[13][13];
/** The theta derivative of p(n,m) (unnormalized) */
private double dp[][] = new double[13][13];
/** The Schmidt normalization factors */
private double snorm[] = new double[169];
/** The sine of (m*spherical coordinate longitude) */
private double sp[] = new double[13];
/** The cosine of (m*spherical coordinate longitude) */
private double cp[] = new double[13];
private double fn[] = new double[13];
private double fm[] = new double[13];
/** The maximum order of spherical harmonic model */
private int maxord;
/** The associated Legendre polynomials for m = 1 (unnormalized) */
private double pp[] = new double[13];
private double k[][] = new double[13][13];
/** The date in years, for the start of the valid time of the fit coefficients */
private double epoch;
private double r, d, ca, sa, ct, st;
}
@@ -0,0 +1,50 @@
package com.sap.sailing.declination.impl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.GregorianCalendar;
import java.util.TreeMap;
import com.sap.sailing.declination.Declination;
import com.sap.sailing.declination.DeclinationService;
import com.sap.sailing.declination.impl.Geomagnetism.Result;
import com.sap.sailing.domain.common.Position;
import com.sap.sse.common.Distance;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.DegreeBearingImpl;
public class WMMCalculatorDeclinationService implements DeclinationService {
/**
* The magnetic models, in ascending order by their {@link Geomagnetism#getIssueTimePoint() issue time point}
*/
private final TreeMap<TimePoint, Geomagnetism> worldMagneticModelsByIssueTimePoint;
public WMMCalculatorDeclinationService() {
final String[] modelNames = new String[] { "/WMM2010.COF", "/WMM2015.COF", "/WMM2020.COF", "/WMM2025.COF" };
worldMagneticModelsByIssueTimePoint = new TreeMap<>();
try {
for (final String modelFileName : modelNames) {
final Geomagnetism model = new Geomagnetism(new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(modelFileName))));
worldMagneticModelsByIssueTimePoint.put(model.getStartOfValidity(), model);
}
} catch (IOException | ParseException e) {
throw new RuntimeException(e);
}
}
@Override
public Declination getDeclination(TimePoint timePoint, Position position, long timeoutForOnlineFetchInMilliseconds) throws IOException, ParseException {
final GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(timePoint.asMillis());
final Result wmmResult = worldMagneticModelsByIssueTimePoint.floorEntry(timePoint).getValue().calculate(position.getLngDeg(), position.getLatDeg(), /* altitude */ 0, calendar);
return new DeclinationRecordForExactTimePoint(position, timePoint, new DegreeBearingImpl(wmmResult.getDeclination()));
}
@Override
public Declination getDeclination(TimePoint timePoint, Position position, Distance maxDistance,
long timeoutForOnlineFetchInMilliseconds) throws IOException, ParseException {
return getDeclination(timePoint, position, timeoutForOnlineFetchInMilliseconds);
}
}
@@ -22,8 +22,8 @@ public class ReadCredentialsTest {
try {
((CredentialsImpl) c).fetchToken();
fail("Expected an unauthorized (401) error code");
} catch (IOException e) {
assertTrue(e.getMessage().contains("401")); // expected
} catch (SecurityException e) {
assertTrue(e.getMessage().contains("Authentication failed: Unauthorized")); // expected
}
}
}
@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -123,8 +124,14 @@ public class AICoreImpl implements AICore {
public JSONObject getJSONResponse(HttpUriRequest request) throws UnsupportedOperationException, ClientProtocolException, URISyntaxException, IOException, ParseException {
final CloseableHttpClient client = getHttpClient();
final HttpResponse response = client.execute(request);
if (response.getStatusLine().getStatusCode() >= 400) {
throw new IOException("Error fetching "+request.getRequestLine()+": ("+response.getStatusLine().getStatusCode()+") "+response.getStatusLine().getReasonPhrase());
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 401) {
throw new SecurityException("Authentication failed: "+response.getStatusLine().getReasonPhrase());
} else if (statusCode == 403) {
throw new AccessControlException("Authorization failed: " + response.getStatusLine().getReasonPhrase());
}
if (statusCode >= 400) {
throw new IOException("Error fetching "+request.getRequestLine()+": ("+statusCode+") "+response.getStatusLine().getReasonPhrase());
}
final JSONObject configurationsJson = (JSONObject) new JSONParser().parse(new InputStreamReader(response.getEntity().getContent()));
return configurationsJson;
@@ -6,6 +6,7 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.List;
@@ -119,8 +120,14 @@ public class CredentialsImpl implements Credentials {
.build();
final JSONParser jsonParser = new JSONParser();
final HttpResponse response = client.execute(postRequest);
if (response.getStatusLine().getStatusCode() >= 400) {
throw new IOException("Error obtaining client token: "+response.getStatusLine().getReasonPhrase()+" ("+response.getStatusLine().getStatusCode()+")");
final int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 401) {
throw new SecurityException("Authentication failed: "+response.getStatusLine().getReasonPhrase());
} else if (statusCode == 403) {
throw new AccessControlException("Authorization failed: " + response.getStatusLine().getReasonPhrase());
}
if (statusCode >= 400) {
throw new IOException("Error obtaining client token: "+response.getStatusLine().getReasonPhrase()+" ("+statusCode+")");
}
final JSONObject tokenJson = (JSONObject) jsonParser.parse(new InputStreamReader(response.getEntity().getContent()));
return (String) tokenJson.get(ACCESS_TOKEN);