Skip to content

Commit ee04901

Browse files
committed
SurfacePosition
Initial attempt at implementing SurfacePosition. Algorithm is mostly shared with AirbornePosition, so the common elements are moved to the PositionUpdate class, which maintains a cache of even/odd frames, decides whether Global or Local calculation can be used, and generally contains the complexity of the CPR system. Some further sanity checks are required, and also a way to choose between multiple possible solutions for lat/lon for Surface data.
1 parent b660ef3 commit ee04901

File tree

10 files changed

+521
-193
lines changed

10 files changed

+521
-193
lines changed

examples/gui/src/main/java/example/Demo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
public class Demo extends JPanel {
2424
private static final Logger logger = LoggerFactory.getLogger(Demo.class);
2525

26-
private static final String IP = "192.168.178.190";
26+
private static final String IP = "127.0.0.1";
2727
private static final int PORT = 30002;
2828
private static final double LAT = 51;
2929
private static final double LON = 2;

src/main/java/aero/t2s/modes/CprPosition.java

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,33 @@
55
public class CprPosition {
66
private double lat;
77
private double lon;
8-
private boolean valid;
8+
private boolean surface = false;
9+
private boolean valid = false;
10+
private double latZone;
11+
private double lonZone;
912
private long time;
1013

1114
public CprPosition() {
1215
this.lat = 0.0;
1316
this.lon = 0.0;
17+
this.surface = false;
1418
this.valid = false;
1519
}
20+
21+
public CprPosition(double lat, double lon, boolean surface) {
22+
setLatLon(lat ,lon);
23+
this.surface = surface;
24+
}
25+
1626
public CprPosition(double lat, double lon) {
1727
setLatLon(lat ,lon);
1828
}
1929

30+
public CprPosition(int cprLat, int cprLon, boolean surface) {
31+
setLatLon(cprLat / (double) (1 << 17), cprLon / (double) (1 << 17));
32+
this.surface = surface;
33+
}
34+
2035
public void setLatLon(double lat, double lon) {
2136
this.lat = lat;
2237
this.lon = lon;
@@ -40,6 +55,41 @@ public double getLon() {
4055
return lon;
4156
}
4257

58+
public void setSurface(boolean surface) {
59+
this.surface = surface;
60+
}
61+
62+
public boolean getSurface() {
63+
return this.surface;
64+
}
65+
66+
public void validateSurface() {
67+
// For SurfacePositions we get 8 possible solutions: 2x latitude, 4x longitude zones at 90 degree increments
68+
// TODO Could use receiver/reference lat/lon to chose which possibile solution is closest... or build-up an average over data received to-date
69+
// Just for temporary, assume northern hemisphere and west of prime meridian
70+
this.lon = this.lon - 90.0;
71+
}
72+
73+
public void setZones(double latZone, double lonZone) {
74+
this.latZone = latZone;
75+
this.lonZone = lonZone;
76+
}
77+
public void setLatZone(double zone) {
78+
this.latZone = zone;
79+
}
80+
81+
public double getLatZone() {
82+
return this.latZone;
83+
}
84+
85+
public void setLonZone(double zone) {
86+
this.lonZone = zone;
87+
}
88+
89+
public double getLonZone() {
90+
return this.lonZone;
91+
}
92+
4393
public void setTime(long time) {
4494
this.time = time;
4595
}
@@ -52,6 +102,10 @@ public boolean isValid() {
52102
return valid;
53103
}
54104

105+
public void setValid(boolean valid) {
106+
this.valid = valid;
107+
}
108+
55109
public boolean isExpired() {
56110
return time < Instant.now().minusSeconds(10).toEpochMilli();
57111
}

src/main/java/aero/t2s/modes/ModeSHandler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package aero.t2s.modes;
22

33
import aero.t2s.modes.decoder.df.DownlinkFormat;
4-
import aero.t2s.modes.decoder.df.df17.AirbornePosition;
4+
import aero.t2s.modes.decoder.df.df17.PositionUpdate;
55

66
import java.util.function.Consumer;
77

@@ -43,10 +43,10 @@ protected short[] toData(final String input) throws EmptyMessageException, ModeA
4343
}
4444

4545
public void start() {
46-
AirbornePosition.start();
46+
PositionUpdate.start();
4747
}
4848

4949
public void stop() {
50-
AirbornePosition.stop();
50+
PositionUpdate.stop();
5151
}
5252
}

src/main/java/aero/t2s/modes/decoder/df/DF17.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public DF17 decode() {
4141
case 6:
4242
case 7:
4343
case 8:
44-
extendedSquitter = new SurfacePosition(data);
44+
extendedSquitter = new SurfacePosition(data, getIcao());
4545
break;
4646
case 19:
4747
extendedSquitter = new AirborneVelocity(data);

src/main/java/aero/t2s/modes/decoder/df/DF18.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public DF18 decode() {
2525
case 6:
2626
case 7:
2727
case 8:
28-
extendedSquitter = new SurfacePosition(data);
28+
extendedSquitter = new SurfacePosition(data, getIcao());
2929
break;
3030
case 19:
3131
extendedSquitter = new AirborneVelocity(data);

src/main/java/aero/t2s/modes/decoder/df/df17/AirbornePosition.java

Lines changed: 8 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import aero.t2s.modes.Track;
44
import aero.t2s.modes.CprPosition;
55
import aero.t2s.modes.constants.*;
6-
import aero.t2s.modes.decoder.Common;
76
import aero.t2s.modes.registers.Register05;
87
import aero.t2s.modes.registers.Register05V0;
98
import aero.t2s.modes.registers.Register05V2;
@@ -18,12 +17,9 @@ public class AirbornePosition extends ExtendedSquitter {
1817
private boolean altitudeSourceBaro;
1918
private int altitude;
2019

21-
private boolean positionAvailable;
22-
2320
private double lat;
2421
private double lon;
25-
private static Map<String, PositionUpdate> cache = new HashMap<>();
26-
private static Timer cacheCleanup;
22+
private boolean positionAvailable;
2723

2824
public AirbornePosition(short[] data, String address) {
2925
super(data);
@@ -58,39 +54,14 @@ public AirbornePosition decode() {
5854
cprLon = cprLon | (data[9] << 8);
5955
cprLon = cprLon | data[10];
6056

61-
62-
if (!cache.containsKey(address)) {
63-
if (!isCprEven) {
64-
return this;
65-
}
66-
67-
synchronized (cache) {
68-
cache.putIfAbsent(address, new PositionUpdate(
69-
new CprPosition(cprLat / (double)(1 << 17), cprLon / (double)(1 << 17))
70-
));
71-
}
72-
}
73-
74-
PositionUpdate positionUpdate;
75-
synchronized (cache) {
76-
positionUpdate = cache.get(address);
77-
}
78-
if (isCprEven) {
79-
positionUpdate.setEven(new CprPosition(cprLat / (double) (1 << 17), cprLon / (double) (1 << 17)));
80-
} else {
81-
positionUpdate.setOdd(new CprPosition(cprLat / (double) (1 << 17), cprLon / (double) (1 << 17)));
82-
}
83-
84-
if (positionUpdate.isComplete()) {
85-
calculateGlobal(positionUpdate.even, positionUpdate.odd);
86-
} else if (positionUpdate.isPreviousPositionAvailable() && positionUpdate.isPreviousPositionAvailable()) {
87-
calculateLocal(positionUpdate.odd, true, positionUpdate.previousLat, positionUpdate.previousLon);
88-
}
89-
90-
if (positionAvailable) {
91-
positionUpdate.setPreviousPosition(this.lat, this.lon);
57+
CprPosition newPosition = PositionUpdate.calculate(address, isCprEven, new CprPosition(cprLat, cprLon, false));
58+
if (newPosition != null) {
59+
this.lat = newPosition.getLat();
60+
this.lon = newPosition.getLon();
61+
this.positionAvailable = true;
62+
} else {
63+
this.positionAvailable = false;
9264
}
93-
9465
return this;
9566
}
9667

@@ -230,88 +201,6 @@ private AltitudeSource determineAltitudeSource() {
230201
return AltitudeSource.GNSS_HAE;
231202
}
232203

233-
private void calculateLocal(CprPosition cpr, boolean isOdd, double previousLat, double previousLon) {
234-
235-
double dlat = isOdd ? 360.0 / 59.0 : 360.0 / 60.0;
236-
237-
double j = Math.floor(previousLat / dlat) + Math.floor((previousLat % dlat) / dlat - cpr.getLat() + 0.5);
238-
239-
double newLat = dlat * (j + previousLat);
240-
241-
double nl = NL(newLat) - (isOdd ? 1.0 : 0.0);
242-
double dlon = nl > 0 ? 360.0 / nl : 360;
243-
244-
double m = Math.floor(previousLon / dlon) + Math.floor((previousLon % dlon) / dlon - cpr.getLon() + 0.5);
245-
double newLon = dlon * (m + lon);
246-
247-
//TODO Should be a sanity-check here to make sure the calculated position isn't outside receiver origin range
248-
//TODO Should be a sanity-check here to see if the calculated movement since the last update is too far
249-
this.lat = newLat;
250-
this.lon = newLon;
251-
this.positionAvailable = true;
252-
}
253-
254-
private void calculateGlobal(CprPosition cprEven, CprPosition cprOdd) {
255-
double dLat0 = 360.0 / 60.0;
256-
double dLat1 = 360.0 / 59.0;
257-
258-
double j = Math.floor(59.0 * cprEven.getLat() - 60.0 * cprOdd.getLat() + 0.5);
259-
260-
double latEven = dLat0 * (j % 60.0 + cprEven.getLat());
261-
double latOdd = dLat1 * (j % 59.0 + cprOdd.getLat());
262-
263-
if (latEven >= 270.0 && latEven <= 360.0) {
264-
latEven -= 360.0;
265-
}
266-
267-
if (latOdd >= 270.0 && latOdd <= 360.0) {
268-
latOdd -= 360.0;
269-
}
270-
271-
if (NL(latEven) != NL(latOdd)) {
272-
return;
273-
}
274-
275-
double lat;
276-
double lon;
277-
if (cprEven.getTime() > cprOdd.getTime()) {
278-
double ni = cprN(latEven, 0);
279-
double m = Math.floor(cprEven.getLon() * (NL(latEven) - 1) - cprOdd.getLon() * NL(latEven) + 0.5);
280-
281-
lat = latEven;
282-
lon = (360d / ni) * (m % ni + cprEven.getLon());
283-
} else {
284-
double ni = cprN(latOdd, 1);
285-
double m = Math.floor(cprEven.getLon() * (NL(latOdd) - 1) - cprOdd.getLon() * NL(latOdd) + 0.5);
286-
287-
lat = latOdd;
288-
lon = (360d / ni) * (m % ni + cprOdd.getLon());
289-
}
290-
291-
if (lon > 180d) {
292-
lon -= 360d;
293-
}
294-
295-
//TODO Should be a sanity-check here to make sure the calculated position isn't outside receiver origin range,
296-
this.lat = lat;
297-
this.lon = lon;
298-
this.positionAvailable = true;
299-
}
300-
private double cprN(double lat, double isOdd) {
301-
double nl = NL(lat) - isOdd;
302-
303-
return nl > 1 ? nl : 1;
304-
}
305-
306-
private double NL(double lat) {
307-
if (lat == 0) return 59;
308-
else if (Math.abs(lat) == 87) return 2;
309-
else if (Math.abs(lat) > 87) return 1;
310-
311-
double tmp = 1 - (1 - Math.cos(Math.PI / (2.0 * 15.0))) / Math.pow(Math.cos(Math.PI / 180.0 * Math.abs(lat)), 2);
312-
return Math.floor(2 * Math.PI / Math.acos(tmp));
313-
}
314-
315204
private int calculateAltitude(short[] data, int typeCode) {
316205
// TODO this should use AltitudeEncoding class flagged with mBit false feature.
317206
int n = (data[5] >>> 1) << 4;
@@ -321,66 +210,4 @@ private int calculateAltitude(short[] data, int typeCode) {
321210

322211
return (n * qBit) - 1000;
323212
}
324-
325-
public static void start() {
326-
AirbornePosition.cache.clear();
327-
AirbornePosition.cacheCleanup.schedule(new TimerTask() {
328-
@Override
329-
public void run() {
330-
List<String> expired = new LinkedList<>();
331-
332-
synchronized (cache) {
333-
cache.entrySet().stream().filter(entry -> entry.getValue().isExpired()).forEach(entry -> expired.add(entry.getKey()));
334-
expired.forEach(cache::remove);
335-
}
336-
}
337-
}, 0, 10_000);
338-
}
339-
340-
public static void stop() {
341-
AirbornePosition.cacheCleanup.cancel();
342-
AirbornePosition.cacheCleanup = null;
343-
344-
AirbornePosition.cache.clear();
345-
}
346-
347-
class PositionUpdate {
348-
private CprPosition even;
349-
private CprPosition odd;
350-
351-
352-
private boolean previousPositionAvailable = false;
353-
private double previousLat;
354-
private double previousLon;
355-
356-
public PositionUpdate(CprPosition even) {
357-
this.even = even;
358-
}
359-
360-
public void setEven(CprPosition even) {
361-
this.even = even;
362-
this.odd = null;
363-
}
364-
365-
public void setOdd(CprPosition odd) {
366-
this.odd = odd;
367-
}
368-
369-
public void setPreviousPosition(double lat, double lon) {
370-
this.previousLat = lat;
371-
this.previousLon = lon;
372-
}
373-
374-
public boolean isPreviousPositionAvailable() {
375-
return this.previousPositionAvailable;
376-
}
377-
378-
public boolean isComplete() {
379-
return even != null && odd != null;
380-
}
381-
382-
public boolean isExpired() {
383-
return even.isExpired() || odd.isExpired();
384-
}
385-
}
386213
}

0 commit comments

Comments
 (0)