Skip to content
Draft
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 @@ -21,8 +21,10 @@

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import io.mapsmessaging.state.drone.core.TwinUpdateContext;

import java.math.BigDecimal;
import java.time.Instant;

public abstract class AbstractN2kJsonListener implements N2kJsonListener {
Expand Down Expand Up @@ -50,47 +52,63 @@ protected boolean hasAny(JsonObject packet, String... names) {
}

protected Double getDouble(JsonObject packet, String... names) {
JsonElement jsonElement = getElement(packet, names);
if (jsonElement == null) {
JsonPrimitive primitive = getPrimitive(packet, names);
if (primitive == null || primitive.isBoolean()) {
return null;
}

try {
double value = primitive.getAsDouble();
return Double.isFinite(value) ? value : null;
} catch (NumberFormatException | UnsupportedOperationException exception) {
return null;
}
return jsonElement.getAsDouble();
}

protected Integer getInteger(JsonObject packet, String... names) {
JsonElement jsonElement = getElement(packet, names);
if (jsonElement == null) {
BigDecimal value = getDecimal(packet, names);
if (value == null) {
return null;
}

try {
return value.intValueExact();
} catch (ArithmeticException exception) {
return null;
}
return jsonElement.getAsInt();
}

protected Long getLong(JsonObject packet, String... names) {
JsonElement jsonElement = getElement(packet, names);
if (jsonElement == null) {
BigDecimal value = getDecimal(packet, names);
if (value == null) {
return null;
}

try {
return value.longValueExact();
} catch (ArithmeticException exception) {
return null;
}
return jsonElement.getAsLong();
}

protected String getString(JsonObject packet, String... names) {
JsonElement jsonElement = getElement(packet, names);
if (jsonElement == null) {
JsonPrimitive primitive = getPrimitive(packet, names);
if (primitive == null || !primitive.isString()) {
return null;
}
return jsonElement.getAsString();
return primitive.getAsString();
}

protected boolean isValidLatitude(Double latitude) {
return latitude != null && latitude >= -90.0d && latitude <= 90.0d;
return latitude != null && Double.isFinite(latitude) && latitude >= -90.0d && latitude <= 90.0d;
}

protected boolean isValidLongitude(Double longitude) {
return longitude != null && longitude >= -180.0d && longitude <= 180.0d;
return longitude != null && Double.isFinite(longitude) && longitude >= -180.0d && longitude <= 180.0d;
}

protected Double normalizeDegrees(Double degrees) {
if (degrees == null) {
if (degrees == null || !Double.isFinite(degrees)) {
return null;
}

Expand All @@ -103,12 +121,33 @@ protected Double normalizeDegrees(Double degrees) {
}

protected Double radiansToDegrees(Double radians) {
if (radians == null) {
if (radians == null || !Double.isFinite(radians)) {
return null;
}
return Math.toDegrees(radians);
}

private BigDecimal getDecimal(JsonObject packet, String... names) {
JsonPrimitive primitive = getPrimitive(packet, names);
if (primitive == null || primitive.isBoolean()) {
return null;
}

try {
return new BigDecimal(primitive.getAsString().trim());
} catch (NumberFormatException exception) {
return null;
}
}

private JsonPrimitive getPrimitive(JsonObject packet, String... names) {
JsonElement jsonElement = getElement(packet, names);
if (jsonElement == null || !jsonElement.isJsonPrimitive()) {
return null;
}
return jsonElement.getAsJsonPrimitive();
}

private JsonElement getElement(JsonObject packet, String... names) {
if (packet == null) {
return null;
Expand All @@ -123,4 +162,4 @@ private JsonElement getElement(JsonObject packet, String... names) {

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,33 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class N2kJsonListenerRegistry {

private final Map<Integer, N2kJsonListener> listeners;

public N2kJsonListenerRegistry() {
listeners = new HashMap<>();

register(new N2kPositionJsonListener());
register(new N2kGnssJsonListener());
register(new N2kMotionJsonListener());
register(new N2kHeadingJsonListener());
register(new N2kAttitudeJsonListener());

register(new N2kRateOfTurnJsonListener());
register(new N2kGnssDopsJsonListener());
register(new N2kBatteryStatusJsonListener());
register(new N2kMagneticVariationJsonListener());
register(new N2kWindJsonListener());
register(new N2kEnvironmentalParametersJsonListener());
register(new N2kInverterStatusJsonListener());
this(
new N2kPositionJsonListener(),
new N2kGnssJsonListener(),
new N2kMotionJsonListener(),
new N2kHeadingJsonListener(),
new N2kAttitudeJsonListener(),
new N2kRateOfTurnJsonListener(),
new N2kGnssDopsJsonListener(),
new N2kBatteryStatusJsonListener(),
new N2kMagneticVariationJsonListener(),
new N2kWindJsonListener(),
new N2kEnvironmentalParametersJsonListener(),
new N2kInverterStatusJsonListener());
}

N2kJsonListenerRegistry(N2kJsonListener... listeners) {
this.listeners = new HashMap<>();
for (N2kJsonListener listener : listeners) {
register(listener);
}
}

public N2kJsonListener getListener(int pgn) {
Expand All @@ -52,7 +58,11 @@ public boolean hasListener(int pgn) {
return listeners.containsKey(pgn);
}

private void register(N2kJsonListener listener) {
listeners.put(listener.getPgn(), listener);
void register(N2kJsonListener listener) {
Objects.requireNonNull(listener, "listener must not be null");
N2kJsonListener existing = listeners.putIfAbsent(listener.getPgn(), listener);
if (existing != null) {
throw new IllegalArgumentException("Duplicate N2K JSON listener for PGN " + listener.getPgn());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public static boolean hasCorePosition(DroneTwin droneTwin) {
return droneTwin != null
&& droneTwin.getMmsi() != null
&& droneTwin.getGeoPosition() != null
&& droneTwin.getGeoPosition().getLatitude() != null
&& droneTwin.getGeoPosition().getLongitude() != null;
&& isValidLatitude(droneTwin.getGeoPosition().getLatitude())
&& isValidLongitude(droneTwin.getGeoPosition().getLongitude());
}

public static Long toSecondOfMinute(Instant instant) {
Expand All @@ -45,12 +45,19 @@ public static Long toSecondOfMinute(Instant instant) {
}

public static Double toRadians(Double degrees) {
if (degrees == null) {
if (degrees == null || !Double.isFinite(degrees)) {
return null;
}
return Math.toRadians(normalizeDegrees(degrees));
}

public static Double nonNegativeFinite(Double value) {
if (value == null || !Double.isFinite(value) || value < 0.0d) {
return null;
}
return value;
}

public static double normalizeDegrees(double degrees) {
double normalized = degrees % 360.0d;
if (normalized < 0.0d) {
Expand Down Expand Up @@ -104,11 +111,22 @@ public static String resolveCallsign(DroneTwin droneTwin, String configuredCalls
return null;
}
if (droneTwin.getCallSign() != null && !droneTwin.getCallSign().isBlank()) {
return truncate(droneTwin.getCallSign(), 7);
return truncate(droneTwin.getCallSign().toUpperCase(Locale.ROOT), 7);
}
if (configuredCallsign != null && !configuredCallsign.isBlank()) {
return truncate(configuredCallsign.toUpperCase(Locale.ROOT), 7);
}
return null;
}

private static boolean isValidLatitude(Double latitude) {
return latitude != null && Double.isFinite(latitude) && latitude >= -90.0d && latitude <= 90.0d;
}

private static boolean isValidLongitude(Double longitude) {
return longitude != null && Double.isFinite(longitude) && longitude >= -180.0d && longitude <= 180.0d;
}

public static String truncate(String value, int maxLength) {
if (value == null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public Optional<AisClassBExtendedPositionReport> map(DroneTwin droneTwin) {
report.setRaim(config.getRaim());
report.setTimeStamp(AisMappingSupport.toSecondOfMinute(droneTwin.getNavigationUpdatedAt()));
report.setCog(AisMappingSupport.toRadians(droneTwin.getCourseOverGroundDegrees()));
report.setSog(droneTwin.getGroundSpeedMetersPerSecond());
report.setSog(AisMappingSupport.nonNegativeFinite(droneTwin.getGroundSpeedMetersPerSecond()));
report.setRegionalApplication(0L);
report.setRegionalApplicationB(0L);
report.setTypeOfShip(config.getShipType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import io.mapsmessaging.state.n2k.msg.AisClassBPositionReport;
import io.mapsmessaging.state.n2k.msg.AisMappingSupport;

import java.time.Instant;
import java.util.Optional;

public class AisClassBPositionMapper {
Expand Down Expand Up @@ -54,13 +53,13 @@ public Optional<AisClassBPositionReport> map(DroneTwin droneTwin) {
? config.getPositionAccuracy()
: (Boolean.TRUE.equals(droneTwin.getGpsValid()) ? 1L : 0L));
report.setRaim(config.getRaim());
report.setTimeStamp(toSecondOfMinute(droneTwin.getNavigationUpdatedAt()));
report.setTimeStamp(AisMappingSupport.toSecondOfMinute(droneTwin.getNavigationUpdatedAt()));

report.setCog(toRadians(droneTwin.getCourseOverGroundDegrees()));
report.setSog(droneTwin.getGroundSpeedMetersPerSecond());
report.setCog(AisMappingSupport.toRadians(droneTwin.getCourseOverGroundDegrees()));
report.setSog(AisMappingSupport.nonNegativeFinite(droneTwin.getGroundSpeedMetersPerSecond()));
report.setCommunicationStateInformation(config.getCommunicationStateInformation());
report.setAisTransceiverInformation(config.getAisTransceiverInformation());
report.setHeading(toRadians(droneTwin.getHeadingDegrees()));
report.setHeading(AisMappingSupport.toRadians(droneTwin.getHeadingDegrees()));

report.setRegionalApplication(0L);
report.setRegionalApplicationB(0L);
Expand All @@ -77,42 +76,10 @@ public Optional<AisClassBPositionReport> map(DroneTwin droneTwin) {
}

private boolean isEligible(DroneTwin droneTwin) {
if (droneTwin == null) {
return false;
}
if (droneTwin.getLifecycleStatus() != TwinLifecycleStatus.ACTIVE) {
return false;
}
if (droneTwin.getMmsi() == null) {
return false;
}
if (droneTwin.getGeoPosition() == null) {
return false;
}
if (droneTwin.getGeoPosition().getLatitude() == null) {
return false;
}
if (droneTwin.getGeoPosition().getLongitude() == null) {
return false;
}
if (!Boolean.TRUE.equals(droneTwin.getGpsValid())) {
return false;
}
return droneTwin.getNavigationUpdatedAt() != null;
return AisMappingSupport.hasCorePosition(droneTwin)
&& droneTwin.getLifecycleStatus() == TwinLifecycleStatus.ACTIVE
&& Boolean.TRUE.equals(droneTwin.getGpsValid())
&& droneTwin.getNavigationUpdatedAt() != null;
}

private static Long toSecondOfMinute(Instant instant) {
return AisMappingSupport.toSecondOfMinute(instant);
}

private static Double toRadians(Double degrees) {
if (degrees == null) {
return null;
}
return Math.toRadians(normalizeDegrees(degrees));
}

private static double normalizeDegrees(double degrees) {
return AisMappingSupport.normalizeDegrees(degrees);
}
}
}
Loading