package au.gov.amsa.navigation.ais;
import static com.google.common.base.Optional.fromNullable;
import static com.google.common.base.Optional.of;
import java.util.Comparator;
import rx.Observable;
import rx.Observable.Transformer;
import rx.functions.Func1;
import au.gov.amsa.ais.AisMessage;
import au.gov.amsa.ais.message.AisPosition;
import au.gov.amsa.ais.message.AisPositionA;
import au.gov.amsa.ais.rx.Streams;
import au.gov.amsa.ais.rx.Streams.TimestampedAndLine;
import au.gov.amsa.navigation.Mmsi;
import au.gov.amsa.navigation.VesselClass;
import au.gov.amsa.navigation.VesselPosition;
import au.gov.amsa.navigation.VesselPosition.NavigationalStatus;
import com.google.common.base.Optional;
public class AisVesselPositions {
public static Observable<VesselPosition> positions(Observable<String> nmea) {
return Streams.extract(nmea).filter(isPresent())
// aggregate ship data with the message
.scan(new AisMessageAndVesselData(), AisMessageAndVesselData.aggregate)
// positions only
.filter(isPosition)
// convert to vessel positions
.map(toVesselPosition);
}
public static Transformer<String, VesselPosition> positions() {
return nmea -> positions(nmea);
}
private static Func1<TimestampedAndLine<AisMessage>, Boolean> isPresent() {
return t -> t.getMessage().isPresent();
}
public static Observable<TimestampedAndLine<AisMessage>> sortByTime(
Observable<TimestampedAndLine<AisMessage>> source) {
Comparator<TimestampedAndLine<AisMessage>> comparator = (t1, t2) -> ((Long) t1.getMessage()
.get().time()).compareTo(t2.getMessage().get().time());
return source
// sort by time
.lift(new SortOperator<TimestampedAndLine<AisMessage>>(comparator, 20000000));
}
public static Observable<VesselPosition> positionsSortedByTime(Observable<String> nmea) {
return sortByTime(Streams.extract(nmea))
// aggregate ship data with the message
.scan(new AisMessageAndVesselData(), AisMessageAndVesselData.aggregate)
// positions only, with lat long present
.filter(isPosition)
// convert to vessel positions
.map(toVesselPosition);
}
private static final Func1<AisMessageAndVesselData, Boolean> isPosition = m -> {
if (m.message().isPresent()
&& m.message().get().getMessage().get().message() instanceof AisPosition) {
AisPosition p = (AisPosition) m.message().get().getMessage().get().message();
return (p.getLatitude() != null && p.getLongitude() != null);
} else
return false;
};
private static final Func1<AisMessageAndVesselData, VesselPosition> toVesselPosition = messageAndData -> {
AisPosition p = (AisPosition) messageAndData.message().get().getMessage().get().message();
VesselClass cls;
if (p instanceof AisPositionA)
cls = VesselClass.A;
else
cls = VesselClass.B;
Mmsi id = new Mmsi(p.getMmsi());
Optional<Vessel> vessel = messageAndData.data().get(id);
Optional<Integer> lengthMetres = vessel.isPresent() ? vessel.get().getLengthMetres()
: Optional.<Integer> absent();
Optional<Integer> widthMetres = vessel.isPresent() ? vessel.get().getWidthMetres()
: Optional.<Integer> absent();
Optional<Double> speedMetresPerSecond = p.getSpeedOverGroundKnots() != null ? of(p
.getSpeedOverGroundKnots() * 0.5144444444) : Optional.<Double> absent();
Optional<Integer> shipType = vessel.isPresent() ? vessel.get().getShipType() : Optional
.<Integer> absent();
NavigationalStatus navigationalStatus;
if (p instanceof AisPositionA) {
AisPositionA a = (AisPositionA) p;
if (Util.equals(a.getNavigationalStatus(),
au.gov.amsa.ais.message.NavigationalStatus.AT_ANCHOR))
navigationalStatus = NavigationalStatus.AT_ANCHOR;
else if (Util.equals(a.getNavigationalStatus(),
au.gov.amsa.ais.message.NavigationalStatus.MOORED)) {
navigationalStatus = NavigationalStatus.MOORED;
} else
navigationalStatus = NavigationalStatus.NOT_DEFINED;
} else
navigationalStatus = NavigationalStatus.NOT_DEFINED;
Optional<String> positionAisNmea;
if (p instanceof AisPositionA) {
positionAisNmea = Optional.of(messageAndData.message().get().getLine());
} else
positionAisNmea = Optional.absent();
Optional<String> shipStaticAisNmea;
if (vessel.isPresent())
shipStaticAisNmea = vessel.get().getNmea();
else
shipStaticAisNmea = Optional.absent();
// TODO adjust lat, lon for position of ais set on ship
// given by A,B,C,D? Or instead store the position offset in
// metres in VesselPosition (preferred because RateOfTurn
// (ROT) may enter the picture later).
return VesselPosition.builder()
// cog
.cogDegrees(fromNullable(p.getCourseOverGround()))
// heading
.headingDegrees(fromNullable(toDouble(p.getTrueHeading())))
// speed
.speedMetresPerSecond(speedMetresPerSecond)
// lat
.lat(p.getLatitude())
// lon
.lon(p.getLongitude())
// id
.id(id)
// length
.lengthMetres(lengthMetres)
// width
.widthMetres(widthMetres)
// time
.time(messageAndData.message().get().getMessage().get().time())
// ship type
.shipType(shipType)
// class
.cls(cls)
// at anchor
.navigationalStatus(navigationalStatus)
// position nmea
.positionAisNmea(positionAisNmea)
// ship static nmea
.shipStaticAisNmea(shipStaticAisNmea)
// build it
.build();
};
private static Double toDouble(Number i) {
if (i == null)
return null;
else
return i.doubleValue();
}
}