Skip to content

Commit

Permalink
Add support for AIS message 27 (ktuukkan#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
liosedhel authored Oct 20, 2020
1 parent 672b757 commit b4543e9
Show file tree
Hide file tree
Showing 7 changed files with 474 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ messages are decoded.
|19 |Extended Class B Equipment Position Report
|21 |Aid-to-Navigation Report
|24 |Static Data Report
|27 |Position Report for long range applications

### Raymarine SeaTalk<sup>1</sup>

Expand Down
24 changes: 24 additions & 0 deletions src/main/java/net/sf/marineapi/ais/message/AISMessage27.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package net.sf.marineapi.ais.message;

/**
* Implementation of https://www.navcen.uscg.gov/?pageName=AISMessage27
*
* @author Krzysztof Borowski
*/
public interface AISMessage27 extends AISPositionReport {

/**
* Returns the RAIM flag.
*
* @return {@code true} if RAIM in use, otherwise {@code false}.
*/
boolean getRAIMFlag();


/**
* Returns Position Latency.
*
* @return 0 = Reported position latency is less than 5 seconds; 1 = Reported position latency is greater than 5 seconds = default
*/
int getPositionLatency();
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ private AISMessageFactory() {
parsers.put(19, AISMessage19Parser.class);
parsers.put(21, AISMessage21Parser.class);
parsers.put(24, AISMessage24Parser.class);
parsers.put(27, AisMessage27Parser.class);
}


Expand Down
205 changes: 205 additions & 0 deletions src/main/java/net/sf/marineapi/ais/parser/AisMessage27Parser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package net.sf.marineapi.ais.parser;

import net.sf.marineapi.ais.message.AISMessage27;
import net.sf.marineapi.ais.util.*;

/**
* AIS Message 27 implementation - LONG-RANGE AUTOMATIC IDENTIFCATION SYSTEM BROADCAST MESSAGE
* see: https://www.navcen.uscg.gov/?pageName=AISMessage27
* <p>
* This message is primarily intended for long-range detection of AIS Class A and Class B “SO” equipped vessels (typically by satellite).
* This message has a similar content to Messages 1, 2 and 3,
* but the total number of bits has been compressed
* to allow for increased propagation delays associated with long-range detection.
* Note there is no time stamp in this message.
* The receiving system is expected to provide the time stamp when this message is received.
*
* <pre>
* Field Name Bits (from, to )
* ------------------------------------------------------------------------
* 1 messageID 6 ( 1, 6) - always 27
* 2 repeatIndicator 2 ( 7, 8) - always 3
* 3 userID 30 ( 9, 38)
* 4 positionAccuracy 1 ( 39, 39)
* 5 raimFlag 1 ( 40, 40)
* 6 navigationalStatus 4 ( 41, 44)
* 7 longitude 18 ( 45, 62)
* 8 latitude 17 ( 63, 79)
* 9 speedOverGround 6 ( 80, 85)
* 10 courseOverGround 9 ( 86, 94)
* 11 positionLatency 1 ( 95, 95)
* 12 spare 1 ( 96, 96) - always 0
* ---- +
* sum 96
* </pre>
*
* @author Krzysztof Borowski
*/
public class AisMessage27Parser extends AISMessageParser implements AISMessage27 {

private final static String SEPARATOR = "\n\t";
private final static int POSITIONACCURACY = 0;
private final static int RAIMFLAG = 1;
private final static int NAVIGATIONALSTATUS = 2;
private final static int LONGITUDE = 3;
private final static int LATITUDE = 4;
private final static int SPEEDOVERGROUND = 5;
private final static int COURSEOVERGROUND = 6;
private final static int POSITIONLATENCY = 7;
private final static int SPARE = 8;
private final static int[] FROM = {
38, 38, 49, 44, 62, 79, 85, 94, 95};
private final static int[] TO = {
38, 39, 44, 62, 79, 85, 94, 95, 96};


private boolean fPositionAccuracy;
private boolean fRaimFlag;
private int fNavigationalStatus;
private int fLongitude;
private int fLatitude;
private int fSOG;
private int fCOG;
private int fPositionLatency;

// not available in this Message27 Position Report, filled in with defaults
private int fTrueHeading = 511;
private int fRateOfTurn = -128;
private int fTimeStamp = 60;
private int fManouverIndicator = 0;


public AisMessage27Parser(Sixbit content) {
super(content, 96, 96);
fPositionAccuracy = content.getBoolean(TO[POSITIONACCURACY]);
fRaimFlag = content.getBoolean(TO[RAIMFLAG]);
fNavigationalStatus = content.getInt(FROM[NAVIGATIONALSTATUS], TO[NAVIGATIONALSTATUS]);
if (!NavigationalStatus.isCorrect(fNavigationalStatus))
addViolation(new AISRuleViolation("NavigationalStatus", fNavigationalStatus, NavigationalStatus.RANGE));

fLongitude = content.getAs18BitInt(FROM[LONGITUDE], TO[LONGITUDE]);
if (!Longitude18.isCorrect(fLongitude))
addViolation(new AISRuleViolation("LongitudeInDegrees", fLongitude, Longitude18.RANGE));
fLatitude = content.getAs17BitInt(FROM[LATITUDE], TO[LATITUDE]);
if (!Latitude17.isCorrect(fLatitude))
addViolation(new AISRuleViolation("LatitudeInDegrees", fLatitude, Latitude17.RANGE));

fSOG = content.getInt(FROM[SPEEDOVERGROUND], TO[SPEEDOVERGROUND]);
fCOG = content.getInt(FROM[COURSEOVERGROUND], TO[COURSEOVERGROUND]);

if (!Angle9.isCorrect(fCOG))
addViolation(new AISRuleViolation("CourseOverGround", fCOG, Angle9.RANGE));

fPositionLatency = content.getInt(FROM[POSITIONLATENCY], TO[POSITIONLATENCY]);
}

@Override
public boolean getRAIMFlag() {
return fRaimFlag;
}

@Override
public int getNavigationalStatus() {
return fNavigationalStatus;
}

@Override
public double getRateOfTurn() {
return RateOfTurn.toDegreesPerMinute(fRateOfTurn);
}

@Override
public double getSpeedOverGround() {
return fSOG;
}

@Override
public boolean isAccurate() {
return fPositionAccuracy;
}

@Override
public double getLongitudeInDegrees() {
return Longitude18.toDegrees(fLongitude);
}

@Override
public double getLatitudeInDegrees() {
return Latitude17.toDegrees(fLatitude);
}

@Override
public double getCourseOverGround() {
return fCOG;
}

@Override
public int getTrueHeading() {
return fTrueHeading;
}

@Override
public int getTimeStamp() {
return fTimeStamp;
}

@Override
public int getManouverIndicator() {
return fManouverIndicator;
}

@Override
public boolean hasRateOfTurn() {
return RateOfTurn.isTurnIndicatorAvailable(fRateOfTurn);
}

@Override
public boolean hasSpeedOverGround() {
return SpeedOverGround.isAvailable(fSOG);
}

@Override
public boolean hasCourseOverGround() {
return Angle12.isAvailable(fCOG);
}

@Override
public boolean hasTrueHeading() {
return Angle9.isAvailable(fTrueHeading);
}

@Override
public boolean hasTimeStamp() {
return TimeStamp.isAvailable(fTimeStamp);
}

@Override
public boolean hasLongitude() {
return Longitude18.isAvailable(fLongitude);
}

@Override
public boolean hasLatitude() {
return Latitude17.isAvailable(fLatitude);
}

@Override
public int getPositionLatency() {
return fPositionLatency;
}

public String toString() {
String result = "\tNav st: " + NavigationalStatus.toString(fNavigationalStatus);
result += SEPARATOR + "ROT: " + RateOfTurn.toString(fRateOfTurn);
result += SEPARATOR + "SOG: " + SpeedOverGround.toString(fSOG);
result += SEPARATOR + "Pos acc: " + (fPositionAccuracy ? "high" : "low") + " accuracy";
result += SEPARATOR + "Lon: " + Longitude18.toString(fLongitude);
result += SEPARATOR + "Lat: " + Latitude17.toString(fLatitude);
result += SEPARATOR + "COG: " + Angle9.toString(fCOG);
result += SEPARATOR + "Heading: " + Angle9.getTrueHeadingString(fTrueHeading);
result += SEPARATOR + "Time: " + TimeStamp.toString(fTimeStamp);
result += SEPARATOR + "Man ind: " + ManeuverIndicator.toString(fManouverIndicator);
result += SEPARATOR + "Latency: " + (fPositionLatency == 0 ? "<5s" : ">5s");
return result;
}
}
86 changes: 86 additions & 0 deletions src/main/java/net/sf/marineapi/ais/util/Latitude17.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Latitude17.java
* Copyright (C) 2015 Lázár József
*
* This file is part of Java Marine API.
* <http://ktuukkan.github.io/marine-api/>
*
* Java Marine API is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Java Marine API is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Java Marine API. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.marineapi.ais.util;

import java.text.DecimalFormat;

/**
* Checks a 17-bit signed integer latitude value for validity.
*
*/
public class Latitude17 {
private static final DecimalFormat COORD_FORMAT = new DecimalFormat("##0.000000;-##0.000000");

private static final int MINUTE_PART_MULTIPLIER = 60 * 10;
private static final int MIN_VALUE = -90 * MINUTE_PART_MULTIPLIER;
private static final int MAX_VALUE = 90 * MINUTE_PART_MULTIPLIER;
private static final int DEFAULT_VALUE = 91 * MINUTE_PART_MULTIPLIER;

/** Valid range with default value for "no value" */
public static final String RANGE = "[" + MIN_VALUE + "," + MAX_VALUE + "] + {" + DEFAULT_VALUE + "}";

/**
* Converts the latitude value (in 1/10000 minutes) to degrees.
*
* @param value Int value to convert
* @return The latitude value in degrees
*/
public static double toDegrees(int value) {
return (double)value / (double)MINUTE_PART_MULTIPLIER;
}

/**
* Tells if the given latitude is available, i.e. within expected range.
*
* @param value Latitude value to validate
* @return {@code true} if available, otherwise {@code false}.
*/
public static boolean isAvailable(int value) {
return value >= MIN_VALUE && value <= MAX_VALUE;
}

/**
* Tells if the given value is correct, i.e. within expected range and not
* the no-value.
*
* @param value Latitude value to validate
* @return {@code true} if correct, otherwise {@code false}.
*/
public static boolean isCorrect(int value) {
return isAvailable(value) || (value == DEFAULT_VALUE);
}

/**
* Returns the String representation of given latitude value.
* @param value Value to stringify
* @return "invalid latitude", "latitude not available" or value formatted
* in degrees.
*/
public static String toString(int value) {
if (!isCorrect(value)) {
return "invalid latitude";
} else if (!isAvailable(value)) {
return "latitude not available";
} else {
return COORD_FORMAT.format(toDegrees(value));
}
}
}
Loading

0 comments on commit b4543e9

Please sign in to comment.