java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/impl/WMMFileTest.java
... ...
@@ -9,6 +9,7 @@ import java.io.InputStreamReader;
9 9
import java.text.ParseException;
10 10
import java.util.GregorianCalendar;
11 11
12
+import org.junit.jupiter.api.BeforeAll;
12 13
import org.junit.jupiter.api.Test;
13 14
14 15
import com.sap.sailing.declination.Declination;
... ...
@@ -17,22 +18,55 @@ import com.sap.sailing.domain.common.impl.DegreePosition;
17 18
import com.sap.sse.common.TimePoint;
18 19
19 20
public class WMMFileTest {
21
+ private static Geomagnetism g;
22
+
23
+ @BeforeAll
24
+ public static void setUpForClass() throws IOException, ParseException {
25
+ g = new Geomagnetism(new BufferedReader(new InputStreamReader(WMMFileTest.class.getClassLoader().getResourceAsStream("WMM2025.COF"))));
26
+ }
27
+
20 28
@Test
21 29
public void testWMM2025Reading() throws IOException {
22
- final Geomagnetism g = new Geomagnetism(new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("WMM2025.COF"))));
23 30
assertNotNull(g);
24 31
}
25 32
26 33
@Test
27
- public void testWMM2025Content() throws IOException, ParseException {
28
- final Geomagnetism g = new Geomagnetism(new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("WMMHR2025.COF"))));
34
+ public void testWMM2025Content1() throws IOException, ParseException {
29 35
final double lat = 44.123;
30 36
final double lng = 8.234;
31 37
final GregorianCalendar cal = new GregorianCalendar(2025, 10, 4, 0, 46, 9);
32
- g.calculate(lng, lat, /* altitude */ 0, cal);
33
- final double declinationWMM2025 = g.getDeclination();
38
+ assertWMMEqualToOneTenthOfADegreeToDeclinationService(g, lat, lng, cal);
39
+ }
40
+
41
+ @Test
42
+ public void testWMM2025Content2() throws IOException, ParseException {
43
+ final double lat = 49.234;
44
+ final double lng = 9.777;
45
+ final GregorianCalendar cal = new GregorianCalendar(2025, 2, 7, 10, 22, 17);
46
+ assertWMMEqualToOneTenthOfADegreeToDeclinationService(g, lat, lng, cal);
47
+ }
48
+
49
+ @Test
50
+ public void testWMM2025ContentSouthernHemisphere() throws IOException, ParseException {
51
+ final double lat = -44.123;
52
+ final double lng = -8.234;
53
+ final GregorianCalendar cal = new GregorianCalendar(2026, 8, 1, 3, 26, 7);
54
+ assertWMMEqualToOneTenthOfADegreeToDeclinationService(g, lat, lng, cal);
55
+ }
56
+
57
+ @Test
58
+ public void testWMM2025ContentFarEastEquator() throws IOException, ParseException {
59
+ final double lat = 0.000;
60
+ final double lng = 170.444;
61
+ final GregorianCalendar cal = new GregorianCalendar(2027, 1, 24, 18, 0, 0);
62
+ assertWMMEqualToOneTenthOfADegreeToDeclinationService(g, lat, lng, cal);
63
+ }
64
+
65
+ private void assertWMMEqualToOneTenthOfADegreeToDeclinationService(final Geomagnetism g, final double lat,
66
+ final double lng, final GregorianCalendar cal) throws IOException, ParseException {
67
+ final double declinationWMM2025 = g.calculate(lng, lat, /* altitude */ 0, cal).getDeclination();
34 68
final DeclinationService s = DeclinationService.INSTANCE;
35 69
final Declination declinationFromService = s.getDeclination(TimePoint.of(cal.getTimeInMillis()), new DegreePosition(lat, lng), /* timeout */ 10000);
36
- assertEquals(declinationWMM2025, declinationFromService.getBearing().getDegrees());
70
+ assertEquals(declinationFromService.getBearing().getDegrees(), declinationWMM2025, /* delta in degrees */ 0.15, "Deviation of more than 0.15 degrees; that's too much!");
37 71
}
38 72
}
java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/DeclinationServiceTest.java
... ...
@@ -13,7 +13,7 @@ import org.junit.jupiter.api.Test;
13 13
import com.sap.sailing.declination.Declination;
14 14
import com.sap.sailing.declination.DeclinationService;
15 15
import com.sap.sailing.declination.impl.DeclinationImporter;
16
-import com.sap.sailing.declination.impl.DeclinationServiceImpl;
16
+import com.sap.sailing.declination.impl.DeclinationServiceImplWithStore;
17 17
import com.sap.sailing.domain.common.impl.CentralAngleDistance;
18 18
import com.sap.sailing.domain.common.impl.DegreePosition;
19 19
import com.sap.sse.common.impl.MillisecondsTimePoint;
... ...
@@ -23,7 +23,7 @@ public abstract class DeclinationServiceTest<I extends DeclinationImporter> exte
23 23
24 24
@BeforeEach
25 25
public void setUp() {
26
- service = new DeclinationServiceImpl(new CentralAngleDistance(1./180.*Math.PI), importer);
26
+ service = new DeclinationServiceImplWithStore(new CentralAngleDistance(1./180.*Math.PI), importer);
27 27
}
28 28
29 29
@Test
java/com.sap.sailing.declination.test/src/com/sap/sailing/declination/test/WMMDeclinationServiceTest.java
... ...
@@ -0,0 +1,41 @@
1
+package com.sap.sailing.declination.test;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
4
+import static org.junit.jupiter.api.Assertions.assertNotNull;
5
+
6
+import java.io.IOException;
7
+import java.text.ParseException;
8
+import java.text.SimpleDateFormat;
9
+
10
+import org.junit.jupiter.api.BeforeAll;
11
+import org.junit.jupiter.api.Test;
12
+
13
+import com.sap.sailing.declination.Declination;
14
+import com.sap.sailing.declination.impl.WMMCalculatorDeclinationService;
15
+import com.sap.sailing.domain.common.impl.DegreePosition;
16
+import com.sap.sse.common.impl.MillisecondsTimePoint;
17
+
18
+public class WMMDeclinationServiceTest {
19
+ private static WMMCalculatorDeclinationService service;
20
+ private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
21
+
22
+ @BeforeAll
23
+ public static void setUp() {
24
+ service = new WMMCalculatorDeclinationService();
25
+ }
26
+
27
+ @Test
28
+ public void testSimpleDeclinationQueryMatchedInStore() throws IOException, ClassNotFoundException, ParseException {
29
+ Declination result = service.getDeclination(new MillisecondsTimePoint(simpleDateFormat.parse("2011-02-03").getTime()),
30
+ new DegreePosition(51, -5), /* timeoutForOnlineFetchInMilliseconds */ 3000);
31
+ assertEquals(-3.-14./60., result.getBearing().getDegrees(), 0.25);
32
+ }
33
+
34
+ @Test
35
+ public void testDeclinationQueryNotMatchedInStore() throws IOException, ClassNotFoundException, ParseException {
36
+ Declination result = service.getDeclination(new MillisecondsTimePoint(simpleDateFormat.parse("2020-02-03").getTime()),
37
+ new DegreePosition(51, -5), /* timeoutForOnlineFetchInMilliseconds */ 5000);
38
+ assertNotNull(result);
39
+ assertEquals(-1.531, result.getBearing().getDegrees(), 0.1);
40
+ }
41
+}
java/com.sap.sailing.declination/resources/WMM2010.COF
... ...
@@ -0,0 +1,93 @@
1
+ 2010.0 WMM-2010 12/10/2009
2
+1 0 -29496.6 0.0 11.6 0.0
3
+1 1 -1586.3 4944.4 16.5 -25.9
4
+2 0 -2396.6 0.0 -12.1 0.0
5
+2 1 3026.1 -2707.7 -4.4 -27.2
6
+2 2 1668.6 -576.1 -10.0 -2.2
7
+3 0 1351.1 0.0 -1.4 0.0
8
+3 1 -2352.3 -214.9 -9.4 6.7
9
+3 2 1225.6 245.0 7.2 -3.1
10
+3 3 581.9 -538.3 -2.6 -1.1
11
+4 0 907.3 0.0 0.8 0.0
12
+4 1 813.8 283.9 -0.4 0.6
13
+4 2 120.3 -188.1 -3.1 0.1
14
+4 3 -335.0 180.9 0.8 1.7
15
+4 4 70.3 -329.5 -1.8 2.2
16
+5 0 -232.6 0.0 -0.3 0.0
17
+5 1 360.1 47.0 0.1 0.4
18
+5 2 192.4 196.9 -0.5 -0.6
19
+5 3 -141.1 -119.0 0.1 0.5
20
+5 4 -157.4 16.1 0.6 0.6
21
+5 5 13.7 93.8 0.0 0.0
22
+6 0 67.4 0.0 0.1 0.0
23
+6 1 65.9 -20.6 -0.3 0.1
24
+6 2 73.3 23.6 -0.1 -0.2
25
+6 3 -121.5 54.3 0.0 0.1
26
+6 4 -36.2 -64.4 -0.1 0.0
27
+6 5 13.5 9.0 0.0 0.0
28
+6 6 -64.7 68.0 0.0 0.0
29
+7 0 80.6 0.0 0.0 0.0
30
+7 1 -76.8 -51.4 0.0 0.1
31
+7 2 -8.3 -16.8 0.0 0.0
32
+7 3 56.5 2.3 0.0 0.0
33
+7 4 15.8 23.5 0.0 0.0
34
+7 5 6.4 -2.2 0.0 0.0
35
+7 6 -7.2 8.6 0.0 0.0
36
+7 7 9.8 -3.9 0.0 0.0
37
+8 0 23.6 0.0 0.0 0.0
38
+8 1 9.8 8.4 0.0 0.0
39
+8 2 -17.5 -15.3 0.0 0.0
40
+8 3 -0.4 12.8 0.0 0.0
41
+8 4 -21.1 -7.4 0.0 0.0
42
+8 5 15.3 9.3 0.0 0.0
43
+8 6 13.7 6.7 0.0 0.0
44
+8 7 -16.5 -0.3 0.0 0.0
45
+8 8 -0.3 3.0 0.0 0.0
46
+9 0 5.0 0.0 0.0 0.0
47
+9 1 8.2 -23.3 0.0 0.0
48
+9 2 2.9 11.1 0.0 0.0
49
+9 3 -1.4 9.8 0.0 0.0
50
+9 4 -1.1 -6.4 0.0 0.0
51
+9 5 -13.3 1.5 0.0 0.0
52
+9 6 1.1 4.2 0.0 0.0
53
+9 7 8.9 0.1 0.0 0.0
54
+9 8 -9.3 -1.4 0.0 0.0
55
+9 9 -11.9 9.9 0.0 0.0
56
+10 0 -1.9 0.0 0.0 0.0
57
+10 1 -6.2 3.4 0.0 0.0
58
+10 2 0.3 -0.4 0.0 0.0
59
+10 3 0.6 -0.8 0.0 0.0
60
+10 4 1.7 -0.7 0.0 0.0
61
+10 5 -0.5 0.3 0.0 0.0
62
+10 6 0.2 -1.7 0.0 0.0
63
+10 7 1.7 1.3 0.0 0.0
64
+10 8 -0.2 0.2 0.0 0.0
65
+10 9 0.3 0.4 0.0 0.0
66
+10 10 -1.2 0.0 0.0 0.0
67
+11 0 3.1 0.0 0.0 0.0
68
+11 1 -1.5 -0.9 0.0 0.0
69
+11 2 -2.3 -2.2 0.0 0.0
70
+11 3 2.1 -0.3 0.0 0.0
71
+11 4 -0.9 0.3 0.0 0.0
72
+11 5 0.6 0.9 0.0 0.0
73
+11 6 0.5 0.2 0.0 0.0
74
+11 7 -0.4 0.1 0.0 0.0
75
+11 8 0.1 0.5 0.0 0.0
76
+11 9 -0.1 0.4 0.0 0.0
77
+11 10 -0.3 -0.2 0.0 0.0
78
+11 11 0.2 -0.2 0.0 0.0
79
+12 0 -2.0 0.0 0.0 0.0
80
+12 1 -0.1 0.6 0.0 0.0
81
+12 2 0.5 -0.6 0.0 0.0
82
+12 3 1.3 0.1 0.0 0.0
83
+12 4 -0.5 0.3 0.0 0.0
84
+12 5 0.4 -0.1 0.0 0.0
85
+12 6 0.1 0.1 0.0 0.0
86
+12 7 0.0 -0.1 0.0 0.0
87
+12 8 0.2 0.2 0.0 0.0
88
+12 9 -0.3 0.0 0.0 0.0
89
+12 10 -0.1 0.0 0.0 0.0
90
+12 11 0.0 0.0 0.0 0.0
91
+12 12 0.1 0.0 0.0 0.0
92
+999999999999999999999999999999999999999999999999
93
+999999999999999999999999999999999999999999999999
java/com.sap.sailing.declination/resources/WMM2015.COF
... ...
@@ -0,0 +1,93 @@
1
+ 2015.0 WMM-2015 12/10/2014
2
+1 0 -29404.8 0.0 6.7 0.0
3
+1 1 -1450.9 4652.5 7.7 -25.9
4
+2 0 -2500.0 0.0 -11.5 0.0
5
+2 1 2982.0 -2991.6 -7.1 -30.2
6
+2 2 1677.0 -734.8 -2.2 -23.9
7
+3 0 1363.9 0.0 2.8 0.0
8
+3 1 -2381.0 -82.2 -6.2 5.2
9
+3 2 1236.2 241.8 3.4 -1.0
10
+3 3 525.7 -542.9 -12.2 1.1
11
+4 0 903.1 0.0 -1.1 0.0
12
+4 1 809.4 282.0 -1.6 0.2
13
+4 2 86.2 -158.4 -6.0 6.9
14
+4 3 -309.4 199.8 5.4 3.7
15
+4 4 47.9 -350.1 -5.5 8.6
16
+5 0 -234.4 0.0 -0.5 0.0
17
+5 1 363.1 47.7 0.5 1.0
18
+5 2 187.8 208.4 -0.7 -0.8
19
+5 3 -140.7 -121.3 0.8 1.0
20
+5 4 -151.2 32.3 1.2 3.0
21
+5 5 13.7 99.1 1.0 0.5
22
+6 0 65.9 0.0 -0.6 0.0
23
+6 1 65.6 -19.1 -0.4 0.1
24
+6 2 73.0 25.0 -0.2 -0.4
25
+6 3 -121.5 52.7 0.4 1.2
26
+6 4 -36.2 -64.4 -0.7 0.6
27
+6 5 13.5 9.0 0.1 0.1
28
+6 6 -64.7 68.0 0.8 -0.1
29
+7 0 80.6 0.0 -0.1 0.0
30
+7 1 -76.8 -51.4 -0.3 0.5
31
+7 2 -8.3 -16.8 0.1 0.1
32
+7 3 56.5 2.3 0.4 -0.7
33
+7 4 15.8 23.5 0.0 -0.5
34
+7 5 6.4 -2.2 -0.3 0.3
35
+7 6 -7.2 8.6 0.0 0.0
36
+7 7 9.8 -3.9 0.3 0.2
37
+8 0 23.6 0.0 -0.3 0.0
38
+8 1 9.8 8.4 -0.1 0.1
39
+8 2 -17.5 -15.3 0.3 0.1
40
+8 3 -0.4 12.8 0.1 -0.3
41
+8 4 -21.1 -7.4 0.0 0.0
42
+8 5 15.3 9.3 -0.1 0.2
43
+8 6 13.7 6.7 0.1 0.0
44
+8 7 -16.5 -0.3 0.2 0.0
45
+8 8 -0.3 3.0 0.0 0.0
46
+9 0 5.0 0.0 0.0 0.0
47
+9 1 8.2 -23.3 -0.1 -0.2
48
+9 2 2.9 11.1 -0.1 -0.1
49
+9 3 -1.4 9.8 0.1 0.2
50
+9 4 -1.1 -6.4 0.0 0.1
51
+9 5 -13.3 1.5 0.3 0.0
52
+9 6 1.1 4.2 0.0 0.0
53
+9 7 8.9 0.1 -0.1 0.0
54
+9 8 -9.3 -1.4 -0.1 0.0
55
+9 9 -11.9 9.9 0.1 0.0
56
+10 0 -1.9 0.0 0.0 0.0
57
+10 1 -6.2 3.4 0.0 0.0
58
+10 2 0.3 -0.4 0.0 0.0
59
+10 3 0.6 -0.8 0.0 0.0
60
+10 4 1.7 -0.7 0.0 0.0
61
+10 5 -0.5 0.3 0.0 0.0
62
+10 6 0.2 -1.7 0.0 0.0
63
+10 7 1.7 1.3 0.0 0.0
64
+10 8 -0.2 0.2 0.0 0.0
65
+10 9 0.3 0.4 0.0 0.0
66
+10 10 -1.2 0.0 0.0 0.0
67
+11 0 3.1 0.0 0.0 0.0
68
+11 1 -1.5 -0.9 0.0 0.0
69
+11 2 -2.3 -2.2 0.0 0.0
70
+11 3 2.1 -0.3 0.0 0.0
71
+11 4 -0.9 0.3 0.0 0.0
72
+11 5 0.6 0.9 0.0 0.0
73
+11 6 0.5 0.2 0.0 0.0
74
+11 7 -0.4 0.1 0.0 0.0
75
+11 8 0.1 0.5 0.0 0.0
76
+11 9 -0.1 0.4 0.0 0.0
77
+11 10 -0.3 -0.2 0.0 0.0
78
+11 11 0.2 -0.2 0.0 0.0
79
+12 0 -2.0 0.0 0.0 0.0
80
+12 1 -0.1 0.6 0.0 0.0
81
+12 2 0.5 -0.6 0.0 0.0
82
+12 3 1.3 0.1 0.0 0.0
83
+12 4 -0.5 0.3 0.0 0.0
84
+12 5 0.4 -0.1 0.0 0.0
85
+12 6 0.1 0.1 0.0 0.0
86
+12 7 0.0 -0.1 0.0 0.0
87
+12 8 0.2 0.2 0.0 0.0
88
+12 9 -0.3 0.0 0.0 0.0
89
+12 10 -0.1 0.0 0.0 0.0
90
+12 11 0.0 0.0 0.0 0.0
91
+12 12 0.1 0.0 0.0 0.0
92
+999999999999999999999999999999999999999999999999
93
+999999999999999999999999999999999999999999999999
java/com.sap.sailing.declination/resources/WMM2020.COF
... ...
@@ -0,0 +1,93 @@
1
+ 2020.0 WMM-2020 12/10/2019
2
+ 1 0 -29404.5 0.0 6.7 0.0
3
+ 1 1 -1450.7 4652.9 7.7 -25.1
4
+ 2 0 -2500.0 0.0 -11.5 0.0
5
+ 2 1 2982.0 -2991.6 -7.1 -30.2
6
+ 2 2 1676.8 -734.8 -2.2 -23.9
7
+ 3 0 1363.9 0.0 2.8 0.0
8
+ 3 1 -2381.0 -82.2 -6.2 5.7
9
+ 3 2 1236.2 241.8 3.4 -1.0
10
+ 3 3 525.7 -542.9 -12.2 1.1
11
+ 4 0 903.1 0.0 -1.1 0.0
12
+ 4 1 809.4 282.0 -1.6 0.2
13
+ 4 2 86.2 -158.4 -6.0 6.9
14
+ 4 3 -309.4 199.8 5.4 3.7
15
+ 4 4 47.9 -350.1 -5.5 -5.6
16
+ 5 0 -234.4 0.0 -0.3 0.0
17
+ 5 1 363.1 47.7 0.6 0.1
18
+ 5 2 187.8 208.4 -0.7 2.5
19
+ 5 3 -140.7 -121.3 0.1 -0.9
20
+ 5 4 -151.2 32.2 1.2 3.0
21
+ 5 5 13.7 99.1 1.0 0.5
22
+ 6 0 65.9 0.0 -0.6 0.0
23
+ 6 1 65.6 -19.1 -0.4 0.1
24
+ 6 2 73.0 25.0 0.5 -1.8
25
+ 6 3 -121.5 52.7 1.4 -1.4
26
+ 6 4 -36.2 -64.4 -1.4 0.9
27
+ 6 5 13.5 9.0 -0.0 0.1
28
+ 6 6 -64.7 68.1 0.8 1.0
29
+ 7 0 80.6 0.0 -0.1 0.0
30
+ 7 1 -76.8 -51.4 -0.3 0.5
31
+ 7 2 -8.3 -16.8 -0.1 0.6
32
+ 7 3 56.5 2.3 0.7 -0.7
33
+ 7 4 15.8 23.5 0.2 -0.2
34
+ 7 5 6.4 -2.2 -0.5 -1.2
35
+ 7 6 -7.2 -27.2 -0.8 0.2
36
+ 7 7 9.8 -1.9 1.0 0.3
37
+ 8 0 23.6 0.0 -0.1 0.0
38
+ 8 1 9.8 8.4 0.1 -0.3
39
+ 8 2 -17.5 -15.3 -0.1 0.7
40
+ 8 3 -0.4 12.8 0.5 -0.2
41
+ 8 4 -21.1 -11.8 -0.1 0.5
42
+ 8 5 15.3 14.9 0.4 -0.3
43
+ 8 6 13.7 3.6 0.5 -0.5
44
+ 8 7 -16.5 -6.9 0.0 0.4
45
+ 8 8 -0.3 2.8 0.4 0.1
46
+ 9 0 5.0 0.0 -0.1 0.0
47
+ 9 1 8.2 -23.3 -0.2 -0.3
48
+ 9 2 2.9 11.1 -0.0 0.2
49
+ 9 3 -1.4 9.8 0.4 -0.4
50
+ 9 4 -1.1 -5.1 -0.3 0.4
51
+ 9 5 -13.3 -6.2 -0.0 0.1
52
+ 9 6 1.1 7.8 0.3 -0.0
53
+ 9 7 8.9 0.4 -0.0 -0.2
54
+ 9 8 -9.3 -1.5 -0.0 0.5
55
+ 9 9 -11.9 9.7 -0.4 0.2
56
+ 10 0 -1.9 0.0 0.0 0.0
57
+ 10 1 -6.2 3.4 -0.0 -0.0
58
+ 10 2 -0.1 -0.2 -0.0 0.1
59
+ 10 3 1.7 3.5 0.2 -0.3
60
+ 10 4 -0.9 4.8 -0.1 0.1
61
+ 10 5 0.6 -8.6 -0.2 -0.2
62
+ 10 6 -0.9 -0.1 -0.0 0.1
63
+ 10 7 1.9 -4.2 -0.1 -0.0
64
+ 10 8 1.4 -3.4 -0.2 -0.1
65
+ 10 9 -2.4 -0.1 -0.1 0.2
66
+ 10 10 -3.9 -8.8 -0.0 -0.0
67
+ 11 0 3.0 0.0 -0.0 0.0
68
+ 11 1 -1.4 -0.0 -0.1 -0.0
69
+ 11 2 -2.5 2.6 -0.0 0.1
70
+ 11 3 2.4 -0.5 0.0 0.0
71
+ 11 4 -0.9 -0.4 -0.0 0.2
72
+ 11 5 0.3 0.6 -0.1 -0.0
73
+ 11 6 -0.7 -0.2 0.0 0.0
74
+ 11 7 -0.1 -1.7 -0.0 0.1
75
+ 11 8 1.4 -1.6 -0.1 -0.0
76
+ 11 9 -0.6 -3.0 -0.1 -0.1
77
+ 11 10 0.2 -2.0 -0.1 0.0
78
+ 11 11 3.1 -2.6 -0.1 -0.0
79
+ 12 0 -2.0 0.0 0.0 0.0
80
+ 12 1 -0.1 -1.2 -0.0 -0.0
81
+ 12 2 0.5 0.5 -0.0 0.0
82
+ 12 3 1.3 1.3 0.0 -0.1
83
+ 12 4 -1.2 -1.8 -0.0 0.1
84
+ 12 5 0.7 0.1 -0.0 -0.0
85
+ 12 6 0.3 0.7 0.0 0.0
86
+ 12 7 0.5 -0.1 -0.0 -0.0
87
+ 12 8 -0.2 0.6 0.0 0.1
88
+ 12 9 -0.5 0.2 -0.0 -0.0
89
+ 12 10 0.1 -0.9 -0.0 -0.0
90
+ 12 11 -1.1 -0.0 -0.0 0.0
91
+ 12 12 -0.3 0.5 -0.1 -0.1
92
+999999999999999999999999999999999999999999999999
93
+999999999999999999999999999999999999999999999999
java/com.sap.sailing.declination/resources/WMM2024.COF
... ...
@@ -1,93 +0,0 @@
1
- 2020.0 WMM-2020 12/10/2019
2
- 1 0 -29404.5 0.0 6.7 0.0
3
- 1 1 -1450.7 4652.9 7.7 -25.1
4
- 2 0 -2500.0 0.0 -11.5 0.0
5
- 2 1 2982.0 -2991.6 -7.1 -30.2
6
- 2 2 1676.8 -734.8 -2.2 -23.9
7
- 3 0 1363.9 0.0 2.8 0.0
8
- 3 1 -2381.0 -82.2 -6.2 5.7
9
- 3 2 1236.2 241.8 3.4 -1.0
10
- 3 3 525.7 -542.9 -12.2 1.1
11
- 4 0 903.1 0.0 -1.1 0.0
12
- 4 1 809.4 282.0 -1.6 0.2
13
- 4 2 86.2 -158.4 -6.0 6.9
14
- 4 3 -309.4 199.8 5.4 3.7
15
- 4 4 47.9 -350.1 -5.5 -5.6
16
- 5 0 -234.4 0.0 -0.3 0.0
17
- 5 1 363.1 47.7 0.6 0.1
18
- 5 2 187.8 208.4 -0.7 2.5
19
- 5 3 -140.7 -121.3 0.1 -0.9
20
- 5 4 -151.2 32.2 1.2 3.0
21
- 5 5 13.7 99.1 1.0 0.5
22
- 6 0 65.9 0.0 -0.6 0.0
23
- 6 1 65.6 -19.1 -0.4 0.1
24
- 6 2 73.0 25.0 0.5 -1.8
25
- 6 3 -121.5 52.7 1.4 -1.4
26
- 6 4 -36.2 -64.4 -1.4 0.9
27
- 6 5 13.5 9.0 -0.0 0.1
28
- 6 6 -64.7 68.1 0.8 1.0
29
- 7 0 80.6 0.0 -0.1 0.0
30
- 7 1 -76.8 -51.4 -0.3 0.5
31
- 7 2 -8.3 -16.8 -0.1 0.6
32
- 7 3 56.5 2.3 0.7 -0.7
33
- 7 4 15.8 23.5 0.2 -0.2
34
- 7 5 6.4 -2.2 -0.5 -1.2
35
- 7 6 -7.2 -27.2 -0.8 0.2
36
- 7 7 9.8 -1.9 1.0 0.3
37
- 8 0 23.6 0.0 -0.1 0.0
38
- 8 1 9.8 8.4 0.1 -0.3
39
- 8 2 -17.5 -15.3 -0.1 0.7
40
- 8 3 -0.4 12.8 0.5 -0.2
41
- 8 4 -21.1 -11.8 -0.1 0.5
42
- 8 5 15.3 14.9 0.4 -0.3
43
- 8 6 13.7 3.6 0.5 -0.5
44
- 8 7 -16.5 -6.9 0.0 0.4
45
- 8 8 -0.3 2.8 0.4 0.1
46
- 9 0 5.0 0.0 -0.1 0.0
47
- 9 1 8.2 -23.3 -0.2 -0.3
48
- 9 2 2.9 11.1 -0.0 0.2
49
- 9 3 -1.4 9.8 0.4 -0.4
50
- 9 4 -1.1 -5.1 -0.3 0.4
51
- 9 5 -13.3 -6.2 -0.0 0.1
52
- 9 6 1.1 7.8 0.3 -0.0
53
- 9 7 8.9 0.4 -0.0 -0.2
54
- 9 8 -9.3 -1.5 -0.0 0.5
55
- 9 9 -11.9 9.7 -0.4 0.2
56
- 10 0 -1.9 0.0 0.0 0.0
57
- 10 1 -6.2 3.4 -0.0 -0.0
58
- 10 2 -0.1 -0.2 -0.0 0.1
59
- 10 3 1.7 3.5 0.2 -0.3
60
- 10 4 -0.9 4.8 -0.1 0.1
61
- 10 5 0.6 -8.6 -0.2 -0.2
62
- 10 6 -0.9 -0.1 -0.0 0.1
63
- 10 7 1.9 -4.2 -0.1 -0.0
64
- 10 8 1.4 -3.4 -0.2 -0.1
65
- 10 9 -2.4 -0.1 -0.1 0.2
66
- 10 10 -3.9 -8.8 -0.0 -0.0
67
- 11 0 3.0 0.0 -0.0 0.0
68
- 11 1 -1.4 -0.0 -0.1 -0.0
69
- 11 2 -2.5 2.6 -0.0 0.1
70
- 11 3 2.4 -0.5 0.0 0.0
71
- 11 4 -0.9 -0.4 -0.0 0.2
72
- 11 5 0.3 0.6 -0.1 -0.0
73
- 11 6 -0.7 -0.2 0.0 0.0
74
- 11 7 -0.1 -1.7 -0.0 0.1
75
- 11 8 1.4 -1.6 -0.1 -0.0
76
- 11 9 -0.6 -3.0 -0.1 -0.1
77
- 11 10 0.2 -2.0 -0.1 0.0
78
- 11 11 3.1 -2.6 -0.1 -0.0
79
- 12 0 -2.0 0.0 0.0 0.0
80
- 12 1 -0.1 -1.2 -0.0 -0.0
81
- 12 2 0.5 0.5 -0.0 0.0
82
- 12 3 1.3 1.3 0.0 -0.1
83
- 12 4 -1.2 -1.8 -0.0 0.1
84
- 12 5 0.7 0.1 -0.0 -0.0
85
- 12 6 0.3 0.7 0.0 0.0
86
- 12 7 0.5 -0.1 -0.0 -0.0
87
- 12 8 -0.2 0.6 0.0 0.1
88
- 12 9 -0.5 0.2 -0.0 -0.0
89
- 12 10 0.1 -0.9 -0.0 -0.0
90
- 12 11 -1.1 -0.0 -0.0 0.0
91
- 12 12 -0.3 0.5 -0.1 -0.1
92
-999999999999999999999999999999999999999999999999
93
-999999999999999999999999999999999999999999999999
java/com.sap.sailing.declination/src/com/sap/sailing/declination/DeclinationService.java
... ...
@@ -3,12 +3,10 @@ package com.sap.sailing.declination;
3 3
import java.io.IOException;
4 4
import java.text.ParseException;
5 5
6
-import com.sap.sailing.declination.impl.BGSImporter;
7 6
import com.sap.sailing.declination.impl.DeclinationImporter;
8
-import com.sap.sailing.declination.impl.DeclinationServiceImpl;
7
+import com.sap.sailing.declination.impl.WMMCalculatorDeclinationService;
9 8
import com.sap.sailing.domain.common.Mile;
10 9
import com.sap.sailing.domain.common.Position;
11
-import com.sap.sailing.domain.common.impl.CentralAngleDistance;
12 10
import com.sap.sse.common.Distance;
13 11
import com.sap.sse.common.TimePoint;
14 12
... ...
@@ -17,7 +15,7 @@ public interface DeclinationService {
17 15
* A default implementation with a spatial default precision of 60 {@link Mile#METERS_PER_GEOGRAPHICAL_MILE
18 16
* nautical miles} which equals the length of an arc with one degree on a meridian.
19 17
*/
20
- DeclinationService INSTANCE = new DeclinationServiceImpl(new CentralAngleDistance(1./180.*Math.PI), new BGSImporter());
18
+ DeclinationService INSTANCE = new WMMCalculatorDeclinationService();
21 19
22 20
/**
23 21
* Obtains declination information with the default precision of this declination service in time and space
java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/AbstractDeclinationRecord.java
... ...
@@ -0,0 +1,39 @@
1
+package com.sap.sailing.declination.impl;
2
+
3
+import com.sap.sailing.declination.Declination;
4
+import com.sap.sailing.domain.common.Position;
5
+import com.sap.sse.common.Bearing;
6
+import com.sap.sse.common.TimePoint;
7
+
8
+public abstract class AbstractDeclinationRecord implements Declination {
9
+ private static final long serialVersionUID = 2701783831406402231L;
10
+ private final Position position;
11
+ private final TimePoint timePoint;
12
+ private final Bearing bearing;
13
+
14
+ public AbstractDeclinationRecord(Position position, TimePoint timePoint, Bearing bearing) {
15
+ this.position = position;
16
+ this.timePoint = timePoint;
17
+ this.bearing = bearing;
18
+ }
19
+
20
+ @Override
21
+ public Position getPosition() {
22
+ return position;
23
+ }
24
+
25
+ @Override
26
+ public TimePoint getTimePoint() {
27
+ return timePoint;
28
+ }
29
+
30
+ @Override
31
+ public Bearing getBearing() {
32
+ return bearing;
33
+ }
34
+
35
+ @Override
36
+ public String toString() {
37
+ return ""+getTimePoint()+"@"+getPosition()+": "+getBearing();
38
+ }
39
+}
java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/BGSImporter.java
... ...
@@ -67,7 +67,8 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
67 67
public class BGSImporter extends DeclinationImporter {
68 68
// 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";
69 69
// 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";
70
- 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";
70
+ 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";
71
+ 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";
71 72
72 73
private static class XmlElementHandler extends DefaultHandler {
73 74
private Date dateAsDecimalYear;
... ...
@@ -156,7 +157,8 @@ public class BGSImporter extends DeclinationImporter {
156 157
throws IOException, ParserConfigurationException, SAXException {
157 158
final Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
158 159
cal.setTime(timePoint.asDate());
159
- 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)));
160
+ final int year = cal.get(Calendar.YEAR);
161
+ 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)));
160 162
return getDeclinationFromXml(url.openStream());
161 163
}
162 164
}
java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/DeclinationRecordForExactTimePoint.java
... ...
@@ -0,0 +1,41 @@
1
+package com.sap.sailing.declination.impl;
2
+
3
+import com.sap.sailing.declination.Declination;
4
+import com.sap.sailing.domain.common.Position;
5
+import com.sap.sse.common.Bearing;
6
+import com.sap.sse.common.TimePoint;
7
+import com.sap.sse.common.Util;
8
+
9
+/**
10
+ * A declination record that does not know about any annual change because it has been computed precisely for
11
+ * the time point and position requested. Therefore, no {@link #getBearingCorrectedTo(TimePoint) correction} to
12
+ * any time point other than the one {@link #getTimePoint() requested} can be performed. An exception will
13
+ * be thrown if that happens.
14
+ *
15
+ * @author Axel Uhl (d043530)
16
+ *
17
+ */
18
+public class DeclinationRecordForExactTimePoint extends AbstractDeclinationRecord implements Declination {
19
+ private static final long serialVersionUID = -94512743120385233L;
20
+
21
+ public DeclinationRecordForExactTimePoint(Position position, TimePoint timePoint, Bearing bearing) {
22
+ super(position, timePoint, bearing);
23
+ }
24
+
25
+ @Override
26
+ public Bearing getAnnualChange() {
27
+ return null;
28
+ }
29
+
30
+ @Override
31
+ public Bearing getBearingCorrectedTo(TimePoint timePoint) {
32
+ if (Util.equalsWithNull(timePoint, getTimePoint())) {
33
+ return getBearing();
34
+ } else {
35
+ throw new IllegalArgumentException("Declination computed precisely for " + getTimePoint()
36
+ + " cannot be corrected to any other time point " + timePoint
37
+ + " because we lack knowledge of the annual change here.");
38
+ }
39
+ }
40
+
41
+}
java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/DeclinationRecordImpl.java
... ...
@@ -6,36 +6,16 @@ import com.sap.sse.common.Bearing;
6 6
import com.sap.sse.common.TimePoint;
7 7
import com.sap.sse.common.impl.DegreeBearingImpl;
8 8
9
-public class DeclinationRecordImpl implements Declination {
9
+public class DeclinationRecordImpl extends AbstractDeclinationRecord implements Declination {
10 10
private static final long serialVersionUID = 6918630656182340186L;
11
- private final Position position;
12
- private final TimePoint timePoint;
13
- private final Bearing bearing;
14 11
private final Bearing annualChange;
12
+
15 13
public DeclinationRecordImpl(Position position, TimePoint timePoint, Bearing bearing, Bearing annualChange) {
16
- super();
17
- this.position = position;
18
- this.timePoint = timePoint;
19
- this.bearing = bearing;
14
+ super(position, timePoint, bearing);
20 15
this.annualChange = annualChange;
21 16
}
22 17
23 18
@Override
24
- public Position getPosition() {
25
- return position;
26
- }
27
-
28
- @Override
29
- public TimePoint getTimePoint() {
30
- return timePoint;
31
- }
32
-
33
- @Override
34
- public Bearing getBearing() {
35
- return bearing;
36
- }
37
-
38
- @Override
39 19
public Bearing getAnnualChange() {
40 20
return annualChange;
41 21
}
... ...
@@ -50,5 +30,4 @@ public class DeclinationRecordImpl implements Declination {
50 30
public String toString() {
51 31
return ""+getTimePoint()+"@"+getPosition()+": "+getBearing()+", "+getAnnualChange()+"/year";
52 32
}
53
-
54 33
}
java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/DeclinationServiceImpl.java
... ...
@@ -1,126 +0,0 @@
1
-package com.sap.sailing.declination.impl;
2
-
3
-import java.io.IOException;
4
-import java.text.ParseException;
5
-import java.util.Calendar;
6
-import java.util.GregorianCalendar;
7
-import java.util.HashMap;
8
-import java.util.Map;
9
-
10
-import com.sap.sailing.declination.Declination;
11
-import com.sap.sailing.declination.DeclinationService;
12
-import com.sap.sailing.domain.common.Position;
13
-import com.sap.sailing.domain.common.quadtree.QuadTree;
14
-import com.sap.sse.common.Distance;
15
-import com.sap.sse.common.TimePoint;
16
-
17
-public class DeclinationServiceImpl implements DeclinationService {
18
- private final Distance defaultMaxDistance;
19
- private final DeclinationStore persistentStore;
20
- private final DeclinationImporter declinationImporter;
21
-
22
- /**
23
- * Keys are years
24
- */
25
- private final Map<Integer, QuadTree<Declination>> yearStore;
26
-
27
- /**
28
- * Keys are years
29
- */
30
- private final Map<Integer, QuadTree<Declination>> importerCache;
31
-
32
- public DeclinationServiceImpl(Distance defaultMaxDistance, DeclinationImporter declinationImporter) {
33
- this.declinationImporter = declinationImporter;
34
- this.defaultMaxDistance = defaultMaxDistance;
35
- this.yearStore = new HashMap<Integer, QuadTree<Declination>>();
36
- this.persistentStore = new DeclinationStore(declinationImporter);
37
- this.importerCache = new HashMap<Integer, QuadTree<Declination>>();
38
- }
39
-
40
- @Override
41
- public Declination getDeclination(TimePoint timePoint, Position position,
42
- long timeoutForOnlineFetchInMilliseconds) throws IOException, ParseException {
43
- return getDeclination(timePoint, position, defaultMaxDistance, timeoutForOnlineFetchInMilliseconds);
44
- }
45
-
46
- @Override
47
- public Declination getDeclination(TimePoint timePoint, Position position, Distance maxDistance,
48
- long timeoutForOnlineFetchInMilliseconds) throws IOException, ParseException {
49
- Calendar cal = new GregorianCalendar();
50
- cal.setTime(timePoint.asDate());
51
- Declination result = null;
52
- double minDistance = Double.MAX_VALUE;
53
- int step = -1;
54
- int year = cal.get(Calendar.YEAR);
55
- QuadTree<Declination> set;
56
- while ((set = getYearStore(year)) != null) {
57
- Declination resultForYear;
58
- synchronized (set) {
59
- resultForYear = set.get(position);
60
- }
61
- Distance spatialDistance = resultForYear.getPosition().getDistance(position);
62
- // consider result only if it's closer than maxDistance
63
- if (spatialDistance.compareTo(maxDistance) <= 0) {
64
- double distance = timeAndSpaceDistance(spatialDistance, resultForYear.getTimePoint(), timePoint);
65
- if (distance < minDistance) {
66
- result = resultForYear;
67
- minDistance = distance;
68
- }
69
- }
70
- year += step;
71
- step = -step - (int) Math.signum(step); // alternate around the original year until no more stored declinations are found
72
- }
73
- if (result == null) {
74
- QuadTree<Declination> importerCacheForYear = importerCache.get(year);
75
- if (importerCacheForYear != null) {
76
- synchronized (importerCacheForYear) {
77
- result = importerCacheForYear.get(position);
78
- }
79
- if (result.getPosition().getDistance(position).compareTo(maxDistance) <= 0) {
80
- return result;
81
- // else it's further away from the requested position as demanded by maxDistance
82
- }
83
- }
84
- result = declinationImporter.getDeclination(position, timePoint, timeoutForOnlineFetchInMilliseconds);
85
- if (result != null) {
86
- if (importerCacheForYear == null) {
87
- importerCacheForYear = new QuadTree<Declination>();
88
- importerCache.put(year, importerCacheForYear);
89
- }
90
- synchronized (importerCacheForYear) {
91
- importerCacheForYear.put(result.getPosition(), result);
92
- }
93
- }
94
- }
95
- return result;
96
- }
97
-
98
- private QuadTree<Declination> getYearStore(int year) throws IOException, ParseException {
99
- QuadTree<Declination> result = yearStore.get(year);
100
- if (result == null) {
101
- synchronized (this) {
102
- // make sure we only trigger one invocation of persistentStore.getStoredDeclinations(year) even for multiple threads
103
- result = yearStore.get(year);
104
- if (result == null) {
105
- result = persistentStore.getStoredDeclinations(year);
106
- if (result != null) {
107
- yearStore.put(year, result);
108
- }
109
- }
110
- }
111
- }
112
- return result;
113
- }
114
-
115
- /**
116
- * Computes a measure for a "distance" based on time and space, between two positions and time points records. Being
117
- * six months off is deemed to be as bad as being sixty nautical miles off.
118
- */
119
- static double timeAndSpaceDistance(Distance spatialDistance, TimePoint t1, TimePoint t2) {
120
- double nauticalMileDistance = spatialDistance.getNauticalMiles();
121
- long millisDistance = Math.abs(t1.asMillis()-t2.asMillis());
122
- return ((double) millisDistance)/1000. /*s*/ / 3600. /*h*/ / 24. /*days*/ / 186. /*six months*/ +
123
- nauticalMileDistance/60.;
124
- }
125
-
126
-}
java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/DeclinationServiceImplWithStore.java
... ...
@@ -0,0 +1,126 @@
1
+package com.sap.sailing.declination.impl;
2
+
3
+import java.io.IOException;
4
+import java.text.ParseException;
5
+import java.util.Calendar;
6
+import java.util.GregorianCalendar;
7
+import java.util.HashMap;
8
+import java.util.Map;
9
+
10
+import com.sap.sailing.declination.Declination;
11
+import com.sap.sailing.declination.DeclinationService;
12
+import com.sap.sailing.domain.common.Position;
13
+import com.sap.sailing.domain.common.quadtree.QuadTree;
14
+import com.sap.sse.common.Distance;
15
+import com.sap.sse.common.TimePoint;
16
+
17
+public class DeclinationServiceImplWithStore implements DeclinationService {
18
+ private final Distance defaultMaxDistance;
19
+ private final DeclinationStore persistentStore;
20
+ private final DeclinationImporter declinationImporter;
21
+
22
+ /**
23
+ * Keys are years
24
+ */
25
+ private final Map<Integer, QuadTree<Declination>> yearStore;
26
+
27
+ /**
28
+ * Keys are years
29
+ */
30
+ private final Map<Integer, QuadTree<Declination>> importerCache;
31
+
32
+ public DeclinationServiceImplWithStore(Distance defaultMaxDistance, DeclinationImporter declinationImporter) {
33
+ this.declinationImporter = declinationImporter;
34
+ this.defaultMaxDistance = defaultMaxDistance;
35
+ this.yearStore = new HashMap<Integer, QuadTree<Declination>>();
36
+ this.persistentStore = new DeclinationStore(declinationImporter);
37
+ this.importerCache = new HashMap<Integer, QuadTree<Declination>>();
38
+ }
39
+
40
+ @Override
41
+ public Declination getDeclination(TimePoint timePoint, Position position,
42
+ long timeoutForOnlineFetchInMilliseconds) throws IOException, ParseException {
43
+ return getDeclination(timePoint, position, defaultMaxDistance, timeoutForOnlineFetchInMilliseconds);
44
+ }
45
+
46
+ @Override
47
+ public Declination getDeclination(TimePoint timePoint, Position position, Distance maxDistance,
48
+ long timeoutForOnlineFetchInMilliseconds) throws IOException, ParseException {
49
+ Calendar cal = new GregorianCalendar();
50
+ cal.setTime(timePoint.asDate());
51
+ Declination result = null;
52
+ double minDistance = Double.MAX_VALUE;
53
+ int step = -1;
54
+ int year = cal.get(Calendar.YEAR);
55
+ QuadTree<Declination> set;
56
+ while ((set = getYearStore(year)) != null) {
57
+ Declination resultForYear;
58
+ synchronized (set) {
59
+ resultForYear = set.get(position);
60
+ }
61
+ Distance spatialDistance = resultForYear.getPosition().getDistance(position);
62
+ // consider result only if it's closer than maxDistance
63
+ if (spatialDistance.compareTo(maxDistance) <= 0) {
64
+ double distance = timeAndSpaceDistance(spatialDistance, resultForYear.getTimePoint(), timePoint);
65
+ if (distance < minDistance) {
66
+ result = resultForYear;
67
+ minDistance = distance;
68
+ }
69
+ }
70
+ year += step;
71
+ step = -step - (int) Math.signum(step); // alternate around the original year until no more stored declinations are found
72
+ }
73
+ if (result == null) {
74
+ QuadTree<Declination> importerCacheForYear = importerCache.get(year);
75
+ if (importerCacheForYear != null) {
76
+ synchronized (importerCacheForYear) {
77
+ result = importerCacheForYear.get(position);
78
+ }
79
+ if (result.getPosition().getDistance(position).compareTo(maxDistance) <= 0) {
80
+ return result;
81
+ // else it's further away from the requested position as demanded by maxDistance
82
+ }
83
+ }
84
+ result = declinationImporter.getDeclination(position, timePoint, timeoutForOnlineFetchInMilliseconds);
85
+ if (result != null) {
86
+ if (importerCacheForYear == null) {
87
+ importerCacheForYear = new QuadTree<Declination>();
88
+ importerCache.put(year, importerCacheForYear);
89
+ }
90
+ synchronized (importerCacheForYear) {
91
+ importerCacheForYear.put(result.getPosition(), result);
92
+ }
93
+ }
94
+ }
95
+ return result;
96
+ }
97
+
98
+ private QuadTree<Declination> getYearStore(int year) throws IOException, ParseException {
99
+ QuadTree<Declination> result = yearStore.get(year);
100
+ if (result == null) {
101
+ synchronized (this) {
102
+ // make sure we only trigger one invocation of persistentStore.getStoredDeclinations(year) even for multiple threads
103
+ result = yearStore.get(year);
104
+ if (result == null) {
105
+ result = persistentStore.getStoredDeclinations(year);
106
+ if (result != null) {
107
+ yearStore.put(year, result);
108
+ }
109
+ }
110
+ }
111
+ }
112
+ return result;
113
+ }
114
+
115
+ /**
116
+ * Computes a measure for a "distance" based on time and space, between two positions and time points records. Being
117
+ * six months off is deemed to be as bad as being sixty nautical miles off.
118
+ */
119
+ static double timeAndSpaceDistance(Distance spatialDistance, TimePoint t1, TimePoint t2) {
120
+ double nauticalMileDistance = spatialDistance.getNauticalMiles();
121
+ long millisDistance = Math.abs(t1.asMillis()-t2.asMillis());
122
+ return ((double) millisDistance)/1000. /*s*/ / 3600. /*h*/ / 24. /*days*/ / 186. /*six months*/ +
123
+ nauticalMileDistance/60.;
124
+ }
125
+
126
+}
java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/DeclinationStore.java
... ...
@@ -207,7 +207,7 @@ public class DeclinationStore {
207 207
existingDeclinationRecord = storedDeclinations.get(point);
208 208
}
209 209
if (existingDeclinationRecord == null
210
- || DeclinationServiceImpl.timeAndSpaceDistance(existingDeclinationRecord.getPosition().getDistance(point),
210
+ || DeclinationServiceImplWithStore.timeAndSpaceDistance(existingDeclinationRecord.getPosition().getDistance(point),
211 211
timePoint, existingDeclinationRecord.getTimePoint()) > 0.1) {
212 212
// less than ~6 nautical miles and/or ~.6 months off
213 213
fetchAndAppendDeclination(timePoint, point, importer, out);
java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/Geomagnetism.java
... ...
@@ -2,6 +2,8 @@ package com.sap.sailing.declination.impl;
2 2
3 3
import java.io.BufferedReader;
4 4
import java.io.IOException;
5
+import java.text.ParseException;
6
+import java.text.SimpleDateFormat;
5 7
6 8
/* License Statement from the NOAA
7 9
* The WMM source code is in the public domain and not licensed or
... ...
@@ -14,24 +16,115 @@ import java.io.IOException;
14 16
15 17
import java.util.GregorianCalendar;
16 18
19
+import com.sap.sse.common.Duration;
20
+import com.sap.sse.common.Named;
21
+import com.sap.sse.common.TimePoint;
22
+
17 23
/**
18 24
* <p>
19 25
* Class to calculate magnetic declination, magnetic field strength, inclination etc. for any point on the earth.
20 26
* </p>
21 27
* <p>
22 28
* Adapted from the geomagc software and World Magnetic Model of the NOAA Satellite and Information Service, National
23
- * Geophysical Data Center
29
+ * Geophysical Data Center. Caching removed to make stateless, except for the reading of the coefficients file.
30
+ * Results are now returned as instances of the inner class {@link Result}.
24 31
* </p>
25 32
* http://www.ngdc.noaa.gov/geomag/WMM/DoDWMM.shtml
26 33
* <p>
27 34
* © Deep Pradhan, 2017
28 35
* </p>
29 36
*/
30
-class Geomagnetism {
37
+class Geomagnetism implements Named {
38
+ private static final long serialVersionUID = -2814152697634383730L;
39
+
40
+ class Result {
41
+ /** Geomagnetic declination (decimal degrees) [opposite of variation, positive Eastward/negative Westward] */
42
+ private final double declination;
43
+
44
+ /** Geomagnetic inclination/dip angle (degrees) [positive downward] */
45
+ private final double inclination;
46
+
47
+ /** Geomagnetic field intensity/strength (nano Teslas) */
48
+ private final double intensity;
49
+
50
+ /** Geomagnetic horizontal field intensity/strength (nano Teslas) */
51
+ private final double bh;
52
+
53
+ /** Geomagnetic vertical field intensity/strength (nano Teslas) [positive downward] */
54
+ private final double bz;
55
+
56
+ /** Geomagnetic North South (northerly component) field intensity/strength (nano Tesla) */
57
+ private final double bx;
58
+
59
+ /** Geomagnetic East West (easterly component) field intensity/strength (nano Teslas) */
60
+ private final double by;
61
+
62
+ public Result(double declination, double inclination, double intensity, double bh, double bz, double bx, double by) {
63
+ this.declination = declination;
64
+ this.inclination = inclination;
65
+ this.intensity = intensity;
66
+ this.bh = bh;
67
+ this.bz = bz;
68
+ this.bx = bx;
69
+ this.by = by;
70
+ }
71
+
72
+ /** @return Geomagnetic declination (degrees) [opposite of variation, positive Eastward/negative Westward] */
73
+ double getDeclination() {
74
+ return declination;
75
+ }
76
+
77
+ /** @return Geomagnetic inclination/dip angle (degrees) [positive downward] */
78
+ double getInclination() {
79
+ return inclination;
80
+ }
81
+
82
+ /** @return Geomagnetic field intensity/strength (nano Teslas) */
83
+ double getIntensity() {
84
+ return intensity;
85
+ }
86
+
87
+ /** @return Geomagnetic horizontal field intensity/strength (nano Teslas) */
88
+ double getHorizontalIntensity() {
89
+ return bh;
90
+ }
91
+
92
+ /** @return Geomagnetic vertical field intensity/strength (nano Teslas) [positive downward] */
93
+ double getVerticalIntensity() {
94
+ return bz;
95
+ }
96
+
97
+ /** @return Geomagnetic North South (northerly component) field intensity/strength (nano Tesla) */
98
+ double getNorthIntensity() {
99
+ return bx;
100
+ }
101
+
102
+ /** @return Geomagnetic East West (easterly component) field intensity/strength (nano Teslas) */
103
+ double getEastIntensity() {
104
+ return by;
105
+ }
106
+
107
+ String getModelName() {
108
+ return getName();
109
+ }
110
+
111
+ TimePoint getModelIssueTimePoint() {
112
+ return getIssueTimePoint();
113
+ }
114
+ }
115
+
116
+ private final static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM/dd/yyyy");
117
+
118
+ private final TimePoint startOfValidity;
119
+
31 120
/**
32 121
* Initialise the instance without calculations
122
+ *
123
+ * @param cofReader
124
+ * a reader for a file in .COF format, such as WMM2025.COF; expects to find trailing lines with at least
125
+ * "999999" in them
33 126
*/
34
- Geomagnetism(BufferedReader r) throws IOException {
127
+ Geomagnetism(BufferedReader cofReader) throws IOException, ParseException {
35 128
// Initialize constants
36 129
maxord = MAX_DEG;
37 130
sp[0] = 0;
... ...
@@ -39,12 +132,16 @@ class Geomagnetism {
39 132
dp[0][0] = 0;
40 133
c[0][0] = 0;
41 134
cd[0][0] = 0;
42
- final String headerLine = r.readLine();
43
- epoch = Double.parseDouble(headerLine.trim().split("\\s+")[0]);
135
+ final String headerLine = cofReader.readLine();
136
+ final String[] headerFields = headerLine.trim().split("\\s+");
137
+ epoch = Double.parseDouble(headerFields[0]);
138
+ 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))));
139
+ name = headerFields[1];
140
+ issueTimePoint = TimePoint.of(simpleDateFormat.parse(headerFields[2]));
44 141
String[] tokens;
45 142
double gnm, hnm, dgnm, dhnm;
46 143
String line;
47
- while (!(line=r.readLine()).startsWith("999999")) {
144
+ while (!(line=cofReader.readLine()).startsWith("999999")) {
48 145
tokens = line.trim().split("\\s+");
49 146
final int n = Integer.parseInt(tokens[0]);
50 147
final int m = Integer.parseInt(tokens[1]);
... ...
@@ -84,7 +181,6 @@ class Geomagnetism {
84 181
}
85 182
k[1][1] = 0;
86 183
fm[0] = 0;
87
- otime = oalt = olat = olon = -1000;
88 184
}
89 185
90 186
/**
... ...
@@ -99,7 +195,7 @@ class Geomagnetism {
99 195
* @param calendar
100 196
* Calendar for date of calculation
101 197
*/
102
- void calculate(double longitude, double latitude, double altitude, GregorianCalendar calendar) {
198
+ Result calculate(double longitude, double latitude, double altitude, GregorianCalendar calendar) {
103 199
double rlon = Math.toRadians(longitude), rlat = Math.toRadians(latitude),
104 200
altitudeKm = Double.isNaN(altitude) ? 0 : altitude / 1000,
105 201
yearFraction = calendar.get(GregorianCalendar.YEAR)
... ...
@@ -111,54 +207,46 @@ class Geomagnetism {
111 207
sp[1] = srlon;
112 208
cp[1] = crlon;
113 209
// Convert from geodetic coords. to spherical coords.
114
- if (altitudeKm != oalt || latitude != olat) {
115
- double q = Math.sqrt(a2 - c2 * srlat2), q1 = altitudeKm * q,
116
- q2 = ((q1 + a2) / (q1 + b2)) * ((q1 + a2) / (q1 + b2)),
117
- r2 = ((altitudeKm * altitudeKm) + 2 * q1 + (a4 - c4 * srlat2) / (q * q));
118
- ct = srlat / Math.sqrt(q2 * crlat2 + srlat2);
119
- st = Math.sqrt(1 - (ct * ct));
120
- r = Math.sqrt(r2);
121
- d = Math.sqrt(a2 * crlat2 + b2 * srlat2);
122
- ca = (altitudeKm + d) / r;
123
- sa = c2 * crlat * srlat / (r * d);
124
- }
125
- if (longitude != olon) {
126
- for (int m = 2; m <= maxord; m++) {
127
- sp[m] = sp[1] * cp[m - 1] + cp[1] * sp[m - 1];
128
- cp[m] = cp[1] * cp[m - 1] - sp[1] * sp[m - 1];
129
- }
210
+ double q = Math.sqrt(a2 - c2 * srlat2), q1 = altitudeKm * q,
211
+ q2 = ((q1 + a2) / (q1 + b2)) * ((q1 + a2) / (q1 + b2)),
212
+ r2 = ((altitudeKm * altitudeKm) + 2 * q1 + (a4 - c4 * srlat2) / (q * q));
213
+ ct = srlat / Math.sqrt(q2 * crlat2 + srlat2);
214
+ st = Math.sqrt(1 - (ct * ct));
215
+ r = Math.sqrt(r2);
216
+ d = Math.sqrt(a2 * crlat2 + b2 * srlat2);
217
+ ca = (altitudeKm + d) / r;
218
+ sa = c2 * crlat * srlat / (r * d);
219
+ for (int m = 2; m <= maxord; m++) {
220
+ sp[m] = sp[1] * cp[m - 1] + cp[1] * sp[m - 1];
221
+ cp[m] = cp[1] * cp[m - 1] - sp[1] * sp[m - 1];
130 222
}
131 223
double aor = IAU66_RADIUS / r, ar = aor * aor, br = 0, bt = 0, bp = 0, bpp = 0, par, parp, temp1, temp2;
132 224
for (int n = 1; n <= maxord; n++) {
133 225
ar = ar * aor;
134 226
for (int m = 0, d3 = 1, d4 = (n + m + d3) / d3; d4 > 0; d4--, m += d3) {
135 227
// Compute unnormalized associated legendre polynomials and derivatives via recursion relations
136
- if (altitudeKm != oalt || latitude != olat) {
137
- if (n == m) {
138
- snorm[n + m * 13] = st * snorm[n - 1 + (m - 1) * 13];
139
- dp[m][n] = st * dp[m - 1][n - 1] + ct * snorm[n - 1 + (m - 1) * 13];
140
- }
141
- if (n == 1 && m == 0) {
142
- snorm[n + m * 13] = ct * snorm[n - 1 + m * 13];
143
- dp[m][n] = ct * dp[m][n - 1] - st * snorm[n - 1 + m * 13];
228
+ if (n == m) {
229
+ snorm[n + m * 13] = st * snorm[n - 1 + (m - 1) * 13];
230
+ dp[m][n] = st * dp[m - 1][n - 1] + ct * snorm[n - 1 + (m - 1) * 13];
231
+ }
232
+ if (n == 1 && m == 0) {
233
+ snorm[n + m * 13] = ct * snorm[n - 1 + m * 13];
234
+ dp[m][n] = ct * dp[m][n - 1] - st * snorm[n - 1 + m * 13];
235
+ }
236
+ if (n > 1 && n != m) {
237
+ if (m > n - 2) {
238
+ snorm[n - 2 + m * 13] = 0;
144 239
}
145
- if (n > 1 && n != m) {
146
- if (m > n - 2) {
147
- snorm[n - 2 + m * 13] = 0;
148
- }
149
- if (m > n - 2) {
150
- dp[m][n - 2] = 0;
151
- }
152
- snorm[n + m * 13] = ct * snorm[n - 1 + m * 13] - k[m][n] * snorm[n - 2 + m * 13];
153
- dp[m][n] = ct * dp[m][n - 1] - st * snorm[n - 1 + m * 13] - k[m][n] * dp[m][n - 2];
240
+ if (m > n - 2) {
241
+ dp[m][n - 2] = 0;
154 242
}
243
+ snorm[n + m * 13] = ct * snorm[n - 1 + m * 13] - k[m][n] * snorm[n - 2 + m * 13];
244
+ dp[m][n] = ct * dp[m][n - 1] - st * snorm[n - 1 + m * 13] - k[m][n] * dp[m][n - 2];
155 245
}
156 246
// Time adjust the gauss coefficients
157
- if (yearFraction != otime) {
158
- tc[m][n] = c[m][n] + dt * cd[m][n];
159
- if (m != 0) {
160
- tc[n][m - 1] = c[n][m - 1] + dt * cd[n][m - 1];
161
- }
247
+ tc[m][n] = c[m][n] + dt * cd[m][n];
248
+ if (m != 0) {
249
+ tc[n][m - 1] = c[n][m - 1] + dt * cd[n][m - 1];
162 250
}
163 251
// Accumulate terms of the spherical harmonic expansions
164 252
par = ar * snorm[n + m * 13];
... ...
@@ -193,19 +281,16 @@ class Geomagnetism {
193 281
// bx must be the east-west field component
194 282
// by must be the north-south field component
195 283
// bz must be the vertical field component.
196
- bx = -bt * ca - br * sa;
197
- by = bp;
198
- bz = bt * sa - br * ca;
284
+ final double bx = -bt * ca - br * sa;
285
+ final double by = bp;
286
+ final double bz = bt * sa - br * ca;
199 287
// Compute declination (dec), inclination (dip) and total intensity (ti)
200
- bh = Math.sqrt((bx * bx) + (by * by));
201
- intensity = Math.sqrt((bh * bh) + (bz * bz));
288
+ final double bh = Math.sqrt((bx * bx) + (by * by));
289
+ final double intensity = Math.sqrt((bh * bh) + (bz * bz));
202 290
// Calculate the declination.
203
- declination = Math.toDegrees(Math.atan2(by, bx));
204
- inclination = Math.toDegrees(Math.atan2(bz, bh));
205
- otime = yearFraction;
206
- oalt = altitudeKm;
207
- olat = latitude;
208
- olon = longitude;
291
+ final double declination = Math.toDegrees(Math.atan2(by, bx));
292
+ final double inclination = Math.toDegrees(Math.atan2(bz, bh));
293
+ return new Result(declination, inclination, intensity, bh, bz, bx, by);
209 294
}
210 295
211 296
/**
... ...
@@ -218,8 +303,8 @@ class Geomagnetism {
218 303
* @param altitude
219 304
* Altitude in metres (with respect to WGS-1984 ellipsoid)
220 305
*/
221
- void calculate(double longitude, double latitude, double altitude) {
222
- calculate(longitude, latitude, altitude, new GregorianCalendar());
306
+ Result calculate(double longitude, double latitude, double altitude) {
307
+ return calculate(longitude, latitude, altitude, new GregorianCalendar());
223 308
}
224 309
225 310
/**
... ...
@@ -230,45 +315,30 @@ class Geomagnetism {
230 315
* @param latitude
231 316
* Latitude in decimal degrees
232 317
*/
233
- void calculate(double longitude, double latitude) {
234
- calculate(longitude, latitude, 0);
318
+ Result calculate(double longitude, double latitude) {
319
+ return calculate(longitude, latitude, 0);
235 320
}
236
-
237
- /** @return Geomagnetic declination (degrees) [opposite of variation, positive Eastward/negative Westward] */
238
- double getDeclination() {
239
- return declination;
321
+
322
+ /** @return The date in years, for the start of the valid time of the fit coefficients */
323
+ public double getEpoch() {
324
+ return epoch;
240 325
}
241 326
242
- /** @return Geomagnetic inclination/dip angle (degrees) [positive downward] */
243
- double getInclination() {
244
- return inclination;
327
+ public TimePoint getStartOfValidity() {
328
+ return startOfValidity;
245 329
}
246 330
247
- /** @return Geomagnetic field intensity/strength (nano Teslas) */
248
- double getIntensity() {
249
- return intensity;
331
+ public String getName() {
332
+ return name;
250 333
}
251 334
252
- /** @return Geomagnetic horizontal field intensity/strength (nano Teslas) */
253
- double getHorizontalIntensity() {
254
- return bh;
255
- }
256
-
257
- /** @return Geomagnetic vertical field intensity/strength (nano Teslas) [positive downward] */
258
- double getVerticalIntensity() {
259
- return bz;
260
- }
261
-
262
- /** @return Geomagnetic North South (northerly component) field intensity/strength (nano Tesla) */
263
- double getNorthIntensity() {
264
- return bx;
265
- }
266
-
267
- /** @return Geomagnetic East West (easterly component) field intensity/strength (nano Teslas) */
268
- double getEastIntensity() {
269
- return by;
335
+ public TimePoint getIssueTimePoint() {
336
+ return issueTimePoint;
270 337
}
338
+ private final String name;
271 339
340
+ private final TimePoint issueTimePoint;
341
+
272 342
/** Mean radius of IAU-66 ellipsoid, in km */
273 343
private static final double IAU66_RADIUS = 6371.2;
274 344
... ...
@@ -281,30 +351,6 @@ class Geomagnetism {
281 351
/** The maximum number of degrees of the spherical harmonic model */
282 352
private static final int MAX_DEG = 12;
283 353
284
- /** Geomagnetic declination (decimal degrees) [opposite of variation, positive Eastward/negative Westward] */
285
- private double declination = 0;
286
-
287
- /** Geomagnetic inclination/dip angle (degrees) [positive downward] */
288
- private double inclination = 0;
289
-
290
- /** Geomagnetic field intensity/strength (nano Teslas) */
291
- private double intensity = 0;
292
-
293
- /** Geomagnetic horizontal field intensity/strength (nano Teslas) */
294
- private double bh;
295
-
296
- /** Geomagnetic vertical field intensity/strength (nano Teslas) [positive downward] */
297
- private double bz;
298
-
299
- /** Geomagnetic North South (northerly component) field intensity/strength (nano Tesla) */
300
- private double bx;
301
-
302
- /** Geomagnetic East West (easterly component) field intensity/strength (nano Teslas) */
303
- private double by;
304
-
305
- /** The maximum order of spherical harmonic model */
306
- private int maxord;
307
-
308 354
/** The Gauss coefficients of main geomagnetic model (nt) */
309 355
private double c[][] = new double[300][300];
310 356
... ...
@@ -328,17 +374,14 @@ class Geomagnetism {
328 374
private double fn[] = new double[13];
329 375
private double fm[] = new double[13];
330 376
377
+ /** The maximum order of spherical harmonic model */
378
+ private int maxord;
379
+
331 380
/** The associated Legendre polynomials for m = 1 (unnormalized) */
332 381
private double pp[] = new double[13];
333 382
334 383
private double k[][] = new double[13][13];
335 384
336
- /**
337
- * The variables otime (old time), oalt (old altitude), olat (old latitude), olon (old longitude), are used to store
338
- * the values used from the previous calculation to save on calculation time if some inputs don't change
339
- */
340
- private double otime, oalt, olat, olon;
341
-
342 385
/** The date in years, for the start of the valid time of the fit coefficients */
343 386
private double epoch;
344 387
java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/WMMCalculatorDeclinationService.java
... ...
@@ -0,0 +1,50 @@
1
+package com.sap.sailing.declination.impl;
2
+
3
+import java.io.BufferedReader;
4
+import java.io.IOException;
5
+import java.io.InputStreamReader;
6
+import java.text.ParseException;
7
+import java.util.GregorianCalendar;
8
+import java.util.TreeMap;
9
+
10
+import com.sap.sailing.declination.Declination;
11
+import com.sap.sailing.declination.DeclinationService;
12
+import com.sap.sailing.declination.impl.Geomagnetism.Result;
13
+import com.sap.sailing.domain.common.Position;
14
+import com.sap.sse.common.Distance;
15
+import com.sap.sse.common.TimePoint;
16
+import com.sap.sse.common.impl.DegreeBearingImpl;
17
+
18
+public class WMMCalculatorDeclinationService implements DeclinationService {
19
+ /**
20
+ * The magnetic models, in ascending order by their {@link Geomagnetism#getIssueTimePoint() issue time point}
21
+ */
22
+ private final TreeMap<TimePoint, Geomagnetism> worldMagneticModelsByIssueTimePoint;
23
+
24
+ public WMMCalculatorDeclinationService() {
25
+ final String[] modelNames = new String[] { "/WMM2010.COF", "/WMM2015.COF", "/WMM2020.COF", "/WMM2025.COF" };
26
+ worldMagneticModelsByIssueTimePoint = new TreeMap<>();
27
+ try {
28
+ for (final String modelFileName : modelNames) {
29
+ final Geomagnetism model = new Geomagnetism(new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(modelFileName))));
30
+ worldMagneticModelsByIssueTimePoint.put(model.getStartOfValidity(), model);
31
+ }
32
+ } catch (IOException | ParseException e) {
33
+ throw new RuntimeException(e);
34
+ }
35
+ }
36
+
37
+ @Override
38
+ public Declination getDeclination(TimePoint timePoint, Position position, long timeoutForOnlineFetchInMilliseconds) throws IOException, ParseException {
39
+ final GregorianCalendar calendar = new GregorianCalendar();
40
+ calendar.setTimeInMillis(timePoint.asMillis());
41
+ final Result wmmResult = worldMagneticModelsByIssueTimePoint.floorEntry(timePoint).getValue().calculate(position.getLngDeg(), position.getLatDeg(), /* altitude */ 0, calendar);
42
+ return new DeclinationRecordForExactTimePoint(position, timePoint, new DegreeBearingImpl(wmmResult.getDeclination()));
43
+ }
44
+
45
+ @Override
46
+ public Declination getDeclination(TimePoint timePoint, Position position, Distance maxDistance,
47
+ long timeoutForOnlineFetchInMilliseconds) throws IOException, ParseException {
48
+ return getDeclination(timePoint, position, timeoutForOnlineFetchInMilliseconds);
49
+ }
50
+}