Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import de.serosystems.lib1090.exceptions.UnspecifiedFormatError;
import de.serosystems.lib1090.msgs.ModeSDownlinkMsg;
import de.serosystems.lib1090.msgs.PositionMsg;
import de.serosystems.lib1090.msgs.QualifiedAddress;
import de.serosystems.lib1090.msgs.adsb.*;
import de.serosystems.lib1090.msgs.modes.*;
import de.serosystems.lib1090.msgs.tisb.CoarsePositionMsg;
Expand Down Expand Up @@ -43,7 +44,7 @@ public class StatefulModeSDecoder {

private final PositionDecoderSupplier positionDecoderSupplier;
// mapping from icao24 to Decoder, note that we cannot use byte[] as key!
private final Map<ModeSDownlinkMsg.QualifiedAddress, DecoderData> decoderData = new HashMap<>();
private final Map<QualifiedAddress, DecoderData> decoderData = new HashMap<>();
private int afterLastCleanup;
private long latestTimestamp;

Expand Down Expand Up @@ -472,11 +473,10 @@ public ModeSDownlinkMsg decode(String raw_message, boolean noCRC, long timestamp
* @param receiver position for reasonableness test (can be null)
* @return decoded WGS84 position
*/
public Position extractPosition(ModeSDownlinkMsg.QualifiedAddress address, PositionMsg msg, Position receiver) {
public Position extractPosition(QualifiedAddress address, PositionMsg msg, Position receiver) {
if (!msg.hasValidPosition()) {
return null;
}

DecoderData dd = getDecoderData(address);
Position pos = dd.posDec.decodePosition(msg.getCPREncodedPosition(), receiver);

Expand Down Expand Up @@ -564,7 +564,7 @@ public void clearDecoders() {
decoderData.values().removeIf(dd -> latestTimestamp - dd.lastUsed > 3600_000L);
}

private DecoderData getDecoderData (ModeSDownlinkMsg.QualifiedAddress address) {
private DecoderData getDecoderData (QualifiedAddress address) {
DecoderData dd = decoderData.computeIfAbsent(
address,
a -> new DecoderData(positionDecoderSupplier.apply(a))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package de.serosystems.lib1090.cpr;

import de.serosystems.lib1090.msgs.ModeSDownlinkMsg;
import de.serosystems.lib1090.msgs.QualifiedAddress;

import java.util.function.Function;

public interface PositionDecoderSupplier extends Function<ModeSDownlinkMsg.QualifiedAddress, PositionDecoder> {
public interface PositionDecoderSupplier extends Function<QualifiedAddress, PositionDecoder> {

static PositionDecoderSupplier statefulPositionDecoder() {
return address -> new StatefulPositionDecoder();
Expand Down
130 changes: 22 additions & 108 deletions src/main/java/de/serosystems/lib1090/msgs/ModeSDownlinkMsg.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,92 +106,6 @@ public enum subtype {
}
private subtype type;

public static class QualifiedAddress {
/**
* Different types of addresses in the AA field (see Table 2-11 in DO-260B)
*/
public enum Type {
// ICAO 24-bit address
ICAO24,
// NON-ICAO 24-bit address
NON_ICAO,
// Anonymous address or ground vehicle address or fixed obstacle address of transmitting ADS-B Participant
ANONYMOUS, // DF=18 with CF=1 or CF=6 and IMF=1
// 12-bit Mode A code and track file number
MODEA_TRACK, // DF=18 with CF=2/3 and IMF=1
// TIS-B/ADS-R management information
TISB_MANAGEMENT_INFO, // DF=18 with CF=4
// Reserved (e.g. for military use)
RESERVED, // DF=19 with AF>0 or DF=18 with CF=5 and IMF=1 or DF=18 and CF=7
// Not (yet) determined
UNKNOWN
}

private int address;
private Type type;

/**
* protected no-arg constructor e.g. internal usage or for serialization with Kryo
**/
protected QualifiedAddress() { }

public QualifiedAddress(int address, Type type) {
this.address = address;
this.type = type;
}

public QualifiedAddress(String address, Type type) {
this(Integer.parseInt(address, 16), type);
}

/**
* @return type of address (e.g. ICAO 24-bit)
*/
public Type getType() {
return type;
}

/**
* @return the address in integer representation
*/
public int getAddress() {
return address;
}

/**
* @return address as 6 digit hex string
*/
public String getHexAddress() {
return Tools.toHexString(address, 6);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

QualifiedAddress that = (QualifiedAddress) o;

if (address != that.address) return false;
return type == that.type;
}

@Override
public int hashCode() {
int result = address;
result = 31 * result + (type != null ? type.hashCode() : 0);
return result;
}

@Override
public String toString() {
return "QualifiedAddress{" +
"address=" + address +
", type=" + type +
'}';
}
}

private QualifiedAddress address;

/*
Expand Down Expand Up @@ -393,7 +307,8 @@ public ModeSDownlinkMsg(byte[] reply, boolean noCRC) throws BadFormatException,
parity = rawAPToInt(Arrays.copyOfRange(reply,reply.length-3, reply.length));

// extract ICAO24 address
address = new QualifiedAddress();
QualifiedAddress.Type type = null;
int addr = 0;
switch (downlink_format) {
case 0: // Short air-air (ACAS)
case 4: // Short altitude reply
Expand All @@ -402,14 +317,14 @@ public ModeSDownlinkMsg(byte[] reply, boolean noCRC) throws BadFormatException,
case 20: // Long Comm-B, altitude reply
case 21: // Long Comm-B, identity reply
case 24: // Long Comm-D (ELM)
address.address = noCRC ? parity : calcParityInt()^parity;
addr = noCRC ? parity : calcParityInt()^parity;
break;

case 11: // all call replies
case 17: case 18: case 19: // Extended squitter
byte[] raw_address = new byte[3];
System.arraycopy(payload, 0, raw_address, 0, 3);
address.address = rawAPToInt(raw_address);
addr = rawAPToInt(raw_address);

if (downlink_format == 18 && first_field==4)
throw new UnspecifiedFormatError("TIS-B/ADS-R management frames not implemented.");
Expand All @@ -431,48 +346,49 @@ else if (downlink_format == 19 && first_field != 0)
// check CF
switch (first_field) {
case 0:
address.type = QualifiedAddress.Type.ICAO24;
type = QualifiedAddress.Type.ICAO24;
break;
case 1:
address.type = QualifiedAddress.Type.ANONYMOUS;
type = QualifiedAddress.Type.ANONYMOUS;
break;
case 2:
case 5:
case 6:
Boolean imf = extractIMF(payload);
if (imf == null)
address.type = QualifiedAddress.Type.UNKNOWN;
type = QualifiedAddress.Type.UNKNOWN;
else if (first_field == 2) // TIS-B
address.type = imf ? QualifiedAddress.Type.MODEA_TRACK : QualifiedAddress.Type.ICAO24;
type = imf ? QualifiedAddress.Type.MODEA_TRACK : QualifiedAddress.Type.ICAO24;
else if (first_field == 5) // TIS-B
address.type = imf ? QualifiedAddress.Type.RESERVED : QualifiedAddress.Type.NON_ICAO;
type = imf ? QualifiedAddress.Type.RESERVED : QualifiedAddress.Type.NON_ICAO;
else // first_field == 6 // ADS-R
address.type = imf ? QualifiedAddress.Type.ANONYMOUS : QualifiedAddress.Type.ICAO24;
type = imf ? QualifiedAddress.Type.ANONYMOUS : QualifiedAddress.Type.ICAO24;
break;
case 3:
// coarse position
if ((payload[3]&0x80) > 0) // IMF field
address.type = QualifiedAddress.Type.ICAO24;
type = QualifiedAddress.Type.ICAO24;
else
address.type = QualifiedAddress.Type.MODEA_TRACK;
type = QualifiedAddress.Type.MODEA_TRACK;

break;
case 4:
address.type = QualifiedAddress.Type.TISB_MANAGEMENT_INFO;
type = QualifiedAddress.Type.TISB_MANAGEMENT_INFO;
break;
case 7:
address.type = QualifiedAddress.Type.RESERVED;
type = QualifiedAddress.Type.RESERVED;
break;
default:
address.type = QualifiedAddress.Type.UNKNOWN;
type = QualifiedAddress.Type.UNKNOWN;
}
} else if (downlink_format == 19) {
// check AF field
address.type = first_field == 0 ? QualifiedAddress.Type.ICAO24 : QualifiedAddress.Type.RESERVED;
type = first_field == 0 ? QualifiedAddress.Type.ICAO24 : QualifiedAddress.Type.RESERVED;
} else {
address.type = QualifiedAddress.Type.ICAO24;
type = QualifiedAddress.Type.ICAO24;
}

address = new QualifiedAddress(addr, type);
setType(subtype.MODES_REPLY);
}

Expand Down Expand Up @@ -528,9 +444,7 @@ public ModeSDownlinkMsg(ModeSDownlinkMsg reply) {
parity = reply.parity;
type = reply.type;
noCRC = reply.noCRC;
address = new QualifiedAddress();
address.address = reply.address.address;
address.type = reply.address.type;
address = new QualifiedAddress(reply.address);
}

/**
Expand Down Expand Up @@ -687,8 +601,8 @@ public boolean equals(Object o) {
return true;
}

return this.getAddress().address == other.getParity() ||
this.getParity() == other.getAddress().address;
return this.getAddress().getAddress() == other.getParity() ||
this.getParity() == other.getAddress().getAddress();
}

@Override
Expand All @@ -708,7 +622,7 @@ public int hashCode() {
int result = downlink_format;
result = 31 * result + (int) first_field;
result = 31 * result + Arrays.hashCode(payload);
result = 31 * result + address.address;
result = 31 * result + address.getAddress();

int effective_parity = parity;
if (noCRC) effective_parity = parity^ calcParityInt();
Expand Down
97 changes: 97 additions & 0 deletions src/main/java/de/serosystems/lib1090/msgs/QualifiedAddress.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package de.serosystems.lib1090.msgs;

import de.serosystems.lib1090.Tools;

/**
* @author Markus Fuchs (fuchs@sero-systems.de)
*/
public class QualifiedAddress {
/**
* Different types of addresses in the AA field (see Table 2-11 in DO-260B)
*/
public enum Type {
// ICAO 24-bit address
ICAO24,
// NON-ICAO 24-bit address
NON_ICAO,
// Anonymous address or ground vehicle address or fixed obstacle address of transmitting ADS-B Participant
ANONYMOUS, // DF=18 with CF=1 or CF=6 and IMF=1
// 12-bit Mode A code and track file number
MODEA_TRACK, // DF=18 with CF=2/3 and IMF=1
// TIS-B/ADS-R management information
TISB_MANAGEMENT_INFO, // DF=18 with CF=4
// Reserved (e.g. for military use)
RESERVED, // DF=19 with AF>0 or DF=18 with CF=5 and IMF=1 or DF=18 and CF=7
// Not (yet) determined
UNKNOWN
}

private int address;
private Type type;

/**
* protected no-arg constructor e.g. internal usage or for serialization with Kryo
**/
protected QualifiedAddress() {
}

public QualifiedAddress(int address, Type type) {
this.address = address;
this.type = type;
}

public QualifiedAddress(QualifiedAddress other) {
this(other.address, other.type);
}

public QualifiedAddress(String address, Type type) {
this(Integer.parseInt(address, 16), type);
}

/**
* @return type of address (e.g. ICAO 24-bit)
*/
public Type getType() {
return type;
}

/**
* @return the address in integer representation
*/
public int getAddress() {
return address;
}

/**
* @return address as 6 digit hex string
*/
public String getHexAddress() {
return Tools.toHexString(address, 6);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

QualifiedAddress that = (QualifiedAddress) o;

if (address != that.address) return false;
return type == that.type;
}

@Override
public int hashCode() {
int result = address;
result = 31 * result + (type != null ? type.hashCode() : 0);
return result;
}

@Override
public String toString() {
return "QualifiedAddress{" +
"address=" + address +
", type=" + type +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,12 @@ public VelocityOverGroundMsg(ExtendedSquitter squitter) throws BadFormatExceptio

vertical_source = (msg[4]&0x10)>0;
vertical_rate_down = (msg[4]&0x08)>0;
vertical_rate = (short) ((((msg[4]&0x07)<<6 | (msg[5]>>>2)&0x3F)-1)<<6);
int raw_vr = ((msg[4]&0x07)<<6 | (msg[5]>>>2)&0x3F);
if (raw_vr == 0) {
vertical_rate_info_available = false;
} else {
vertical_rate = (short) ((raw_vr-1)<<6);
}

geo_minus_baro = msg[6]&0x7F;
if (geo_minus_baro == 0) geo_minus_baro_available = false;
Expand Down Expand Up @@ -228,7 +233,7 @@ public Integer getGeoMinusBaro() {
* @return heading in decimal degrees ([0, 360]) clockwise from geographic north or null if information is not available.
* The latter can also be checked with {@link #hasVelocityInfo()}.
*/
public Double getHeading() {
public Double getTrueTrackAngle() {
if (!velocity_info_available) return null;
double angle = Math.toDegrees(Math.atan2(
-this.getEastToWestVelocity(),
Expand Down