/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package jaid.ais.message;
import exception.InvalidPositionException;
import java.util.Calendar;
import nav.position.Position;
import nav.util.string.StringUtil;
/**
* Represents an AIS message. Superclass to the various AIS message types.
* See ITU-R M.1371-3 for information.
*
* @author Benjamin Jakobus
* @since 1.0
*/
public class AISMsg {
/* A constant denoting that a field/attribute is unknown */
public final static int UNKNOWN = -1;
/* A constant denoting that the origin of this message cannot contain a
* diven attribute.
*/
public final static int NOT_AVAILABLE = -2;
/* The vessel's name. */
private String name;
/* Message type. */
private int messageType;
/* Message repeat indicator. */
private int repeatIndicator;
/* Message time stamp. */
private long timestamp;
/* Message time stamp. */
private String timestampStr;
/* Entity MMSI. */
private String mmsi;
/* Type of EPFS in use. */
private String epfs;
/* Position Accuracy. */
private String posAccuracy;
/* Positio.n */
private Position position;
/* Length/width dimensions - see ITU-1371-3, fig. 44 for explanation. */
int a;
int b;
int c;
int d;
private static final int CLOCK_JITTER = 2;
/* Initialize dimension parameters to default of -1. */
{ a = -1; b = -1; c = -1; d = -1; name = null; }
/**
* Decodes the vessel's name.
*
* @param input A raw AIS data feed.
* @since 1.0
*/
public void decodeName(String input) {
name = decode6BitBinary(input);
}
public float getMaxDraft() {
return -1;
}
/**
* Decodes the entity's position.
*
* @param input A raw AIS data feed.
* @since 1.0
*/
public void decodePosition(String input) {
// Longitude (0-29)
float lng = extractPosition(input.substring(0, 28));
// Latitude (29-116)
float lat = extractPosition(input.substring(28));
// Init the position vessel's position
try {
position = new Position(lat, lng);
} catch (InvalidPositionException ipe) {
try {
position = new Position(0, 0);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Decode the target's heading.
*
* @param input A raw AIS data feed.
* @return heading The target's heading.
* @since 1.0
*/
public String decodeHdg(int input) {
String output = "";
StringUtil u = new StringUtil();
if (input == 511) {
output = "n/a";
} else {
output = String.valueOf(u.padNumZero(input, 3));
}
return output;
}
/**
* Converts a 6-bit binary string into and 8-bit ASCII string.
*
* @param input 6-bit binary string
* @return 8-bit ASCII string.
* @since 1.0
*/
public String decode6BitBinary(String input) {
String output = "";
int loop = input.length() / 6;
for (int i = 0; i < loop; i++) {
// Avoid trailing @ in ASCII strings
String char6 = input.substring(0, 6);
if (char6.compareTo("000000") == 0) {
break;
}
input = input.substring(6);
int asciiNum = bin2int(char6);
// 6 bit binary to 8 bit binary conversion
if (asciiNum < 32) {
asciiNum += 64;
}
output += (char) asciiNum;
}
return output.trim();
}
/**
* Returns the AID type if the sender is a base station
* Overridden by children.
*
* @return AID type
* @since 1.0
*/
public String getAidType() {
return "NA";
}
/**
* Decodes the EPFS type data.
*
* @param input A raw AIS data feed.
* @since 1.0
*/
public void decodeEPFSType(String input) {
switch (Integer.parseInt(input, 2)) {
case 0:
epfs = "Undefined";
break;
case 1:
epfs = "GPS";
break;
case 2:
epfs = "GLONASS";
break;
case 3:
epfs = "GPS+GLONASS";
break;
case 4:
epfs = "Loran-C";
break;
case 5:
epfs = "Chayka";
break;
case 6:
epfs = "INS";
break;
case 7:
epfs = "Surveyed";
break;
case 8:
epfs = "GALILEO";
break;
default:
epfs = "?: " + Integer.parseInt(input, 2);
}
}
/**
* Decodes the EPFS position accuracy.
*
* @param input A raw AIS data feed.
* @since 1.0
*/
public void decodePosAccuracy(String input) {
switch (Integer.parseInt(input, 2)) {
case 0:
posAccuracy = "Low";
break;
case 1:
posAccuracy = "High";
break;
default:
posAccuracy = Integer.parseInt(input, 2) + ": ??";
}
}
/**
* Decodes the vessel dimensions and EPFS sensor position data.
*
* @param input A raw AIS data feed.
* @since 1.0
*/
public void decodeEntityDimensions(String input) {
a = Integer.parseInt(input.substring(0, 9), 2);
input = input.substring(9);
b = Integer.parseInt(input.substring(0, 9), 2);
input = input.substring(9);
c = Integer.parseInt(input.substring(0, 6), 2);
input = input.substring(6);
d = Integer.parseInt(input.substring(0, 6), 2);
}
/**
* Decodes a time stamp and converts it to a full hh:mm:ss stamp
* To allow for processing delay that may result in the wrong
* minute being assigned - i.e. the next minute instead of the previous,
* I do some 'jitter' checking. This problem occurs because the AIS
* system assumes all equipment involved is UTC locked.
*
* @param input A raw AIS data feed.
* @since 1.0
*/
public void decodeTimeStamp(String input) {
Calendar cal = Calendar.getInstance();
//determine UTC time - time zone & DST conversion
cal.add(
Calendar.MILLISECOND,
-cal.get(Calendar.DST_OFFSET)
- cal.get(Calendar.ZONE_OFFSET));
int hoursNow = cal.get(Calendar.HOUR_OF_DAY);
int minsNow = cal.get(Calendar.MINUTE);
int secsNow = cal.get(Calendar.SECOND);
StringUtil su = new StringUtil();
/* AIS seconds - assumes 'current' minute... due network &
* processing delays, it may in fact be the previous minute
* as we're not GPS hardware time synchronised...
*/
int aisSecs = Integer.parseInt(input, 2);
switch (aisSecs) {
case 60:
timestampStr = "Not Available";
break;
case 61:
timestampStr = "EPFS manual";
break;
case 62:
timestampStr = "EPFS DR";
break;
case 63:
timestampStr = "EPFS U/S";
break;
default:
/* Do a simple check to see if it was 'probably' the previous
* minute... if so, correct the display time. NOTE: the PC
* system clock needs to be pretty close to UTC (within 1-2s)
* for this to work - clock synchronisation is needed regularly!
*/
if ((minsNow * 60) + secsNow + CLOCK_JITTER < (minsNow * 60) + aisSecs) {
switch (minsNow) {
case 0:
minsNow = 59;
switch (hoursNow) {
case 0:
hoursNow = 23;
break;
default:
hoursNow -= 1;
}
break;
default:
minsNow -= 1;
}
}
// Set the calendar time to the AIS message received time - in UTC
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH),
cal.get(Calendar.DAY_OF_MONTH), hoursNow, minsNow, aisSecs);
//Store this as the last update time
timestamp = cal.getTimeInMillis();
Calendar newCal = Calendar.getInstance();
// Current system time in UTC
newCal.add(
Calendar.MILLISECOND,
-newCal.get(Calendar.DST_OFFSET)
- newCal.get(Calendar.ZONE_OFFSET));
// build a string representation of the message time...
timestampStr = String.valueOf(su.padNumZero(hoursNow, 2))
+ ":" + String.valueOf(su.padNumZero(minsNow, 2))
+ ":" + String.valueOf(su.padNumZero(aisSecs, 2));
}
}
/**
* Returns the sender's MMSI.
*
* @return mmsi The sender's MMSI number.
* @since 1.0
*/
public String getMMSI() {
return mmsi;
}
/**
* Returns the sender's name.
*
* @return name The sender's name.
* @since 1.0
*/
public String getName() {
return name;
}
/**
* Returns the message's type.
* See ITU-R M.1371-3 for details.
*
* @return The message type. See ITU-R M.1371-3 for details.
* @since 1.0
*/
public int getMsgType() {
return messageType;
}
/**
* Returns the sender's position accuracy.
*
* @return The sender's position accuracy.
* @since 1.0
*/
public String getPosAccuracy() {
return posAccuracy;
}
/**
* Returns the sender's destination. Overriden by children.
*
* @return Destination or "Unknown" if not known.
* @since 1.0
*/
public String getDestination() {
return "unknown";
}
/**
* Returns the sender's position.
*
* @return The sender's position.
* @since 1.0
*/
public Position getPosition() {
return position;
}
/**
* Returns the message's timestamp.
*
* @return Timestamp of when the message was generated.
* @since 1.0
*/
public String getUtcTimeStampStr() {
return timestampStr;
}
/**
* Returns the mesage's timestamp in milliseconds.
*
* @return timestamp Timestamp (in milliseconds) of when the message was generated.
* @since 1.0
*/
public long getUtcTimeStamp() {
return timestamp;
}
/**
* Returns the message's repeat indicator.
*
* @return Repeat indicator
* @since 1.0
*/
public int getRepeatIndicator() {
return repeatIndicator;
}
/**
* Sets the repeat indicator.
*
* @param repeatIndicator The message's repeat indicator.
* @since 1.0
*/
public void setRepeatIndicator(int repeatIndicator) {
this.repeatIndicator = repeatIndicator;
}
/**
* Sets the message type.
* See ITU-R M.1371-3 for message types.
*
* @param messageType The message type.
* @since 1.0
*/
public void setMessageType(int messageType) {
this.messageType = messageType;
}
/**
* Sets the sender's MMSI.
*
* @param mmsi The sender's MMSI number.
* @since 1.0
*/
public void setMmsi(String mmsi) {
this.mmsi = mmsi;
}
/**
* Prints the message contents.
*
* @return
* @since 1.0
*/
public String print() {
return "";
}
/**
* Decodes the sender's position.
*
* @param sgnBinNum The string contained the sender's encoded position.
* @return The sender's position
* @since 1.0
*/
private float extractPosition(String sgnBinNum) {
//convert binary string into int array for ease of manipulation
if (sgnBinNum.substring(0, 1).compareTo("1") == 0) {
sgnBinNum = twosCompliment(sgnBinNum);
return -((float) Integer.parseInt(sgnBinNum, 2)) / 600000;
} else {
return ((float) Integer.parseInt(sgnBinNum, 2)) / 600000;
}
}
/**
* Processes 2's compliment.
*
* @param input Binary string presented using two's compliment.
* @return Decoded two's complement.
* @since 1.0
*/
public static String twosCompliment(String input) {
char[] bits = input.toCharArray();
String compliment = "";
int i;
for (i = bits.length - 1; i >= 0; i--) {
if (bits[i] == '0') {
//copy '0's unchanged
compliment = bits[i] + compliment;
} else {
//we've found our first 1 - game changes
compliment = bits[i] + compliment;
break;
}
}
//flip all remaining bits...
for (int j = i - 1; j >= 0; j--) {
switch (bits[j]) {
case '1':
compliment = "0" + compliment;
break;
case '0':
compliment = "1" + compliment;
}
}
return compliment;
}
/**
* Returns the target's A-dimension. A target's A-dimension is the distance
* (in meters) between the the target's GPS antenna and its bow.
*
* @return The target's A dimension. Measured in meters.
* @since 1.0
*/
public int getA() {
return a;
}
/**
* Returns the target's B-dimension. A target's B-dimension is the distance
* (in meters) between the the target's GPS antenna and its back.
*
* @return The target's B dimension. Measured in meters.
* @since 1.0
*/
public int getB() {
return b;
}
/**
* Returns the target's C-dimension. A target's C-dimension is the distance
* (in meters) between the the target's GPS antenna and its left side.
*
* @return The target's C dimension. Measured in meters.
* @since 1.0
*/
public int getC() {
return c;
}
/**
* Returns the target's D-dimension. A target's D-dimension is the distance
* (in meters) between the the target's GPS antenna and its right side.
*
* @return The target's D dimension. Measured in meters.
* @since 1.0
*/
public int getD() {
return d;
}
/**
* Returns the sender's EPFS
* @return the EPFS
* @since 1.0
*/
public String getEpfs() {
return epfs;
}
/**
* Sets the message's timestamp as Unix time
* @param timestamp
* @since 1.0
*/
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
/**
* Sets the message's timestamp in string format
* @param timestampStr
* @since 1.0
*/
public void setTimestampStr(String timestampStr) {
this.timestampStr = timestampStr;
}
/**
* Returns the sender's navigational status
* @return the nav status or "unknown" if it is not known
* @since 1.0
*/
public String getNavStatus() {
return "unknown";
}
/**
* Returns the sender's speed. Overriden by children.
* @return the speed or "unknown" if it is not known
* @since 1.0
*/
public float getSpeed() {
return UNKNOWN;
}
/**
* Returns the sender's course. Overriden by children.
* @return the course or "unknown" if it is not known
* @since 1.0
*/
public int getCourse() {
return UNKNOWN;
}
/**
* Returns the sender's heading. Overriden by children.
* @return the heading or "unknown" if it is not known
* @since 1.0
*/
public int getHeading() {
return UNKNOWN;
}
/**
* Returns the sender's Expected Time of Arrival (ETA)
* Overriden by children.
* @return the ETA or unknown if it is not known
* @since 1.0
*/
public String getEta() {
return "unknown";
}
/**
* Returns the type of cargo that the message sender is carrying.
* Overriden by children.
*
* @return the cargo type or unknown if it is not known
* @since 1.0
*/
public String getCargoType() {
return "unknown";
}
/**
* Converts a binary integer string into an integer.
*
* @param str
* @return
* @since 1.0
*/
private int bin2int(String str) {
return (Integer.parseInt(str, 2));
}
}