/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package jaid.ais.data;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.render.GlobeAnnotation;
import gov.nasa.worldwind.render.Polygon;
import gov.nasa.worldwind.render.Polyline;
import gov.nasa.worldwind.render.SurfaceIcon;
import jaid.ais.message.*;
import java.awt.Color;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import nav.position.Position;
import nav.util.math.NavCalculator;
import ui.layers.TargetLayer;
import ui.layers.TargetTextLayer;
/**
* The <code>Target</code> class represents the notion of a maritime or aerospace
* entity identified either via radar or AIS. Consequently, the class
* encapsulates the all properties that identify and entity and that define its
* position and physical characteristics. Every target object is associated with
* the data source(s) that are currently tracking / identifying it.
*
* @author Benjamin Jakobus
* @version 1.0
* @since 1.0
*/
public class Target {
/**
* Define constants that indicate the target's type.
*/
/* Indicates that the target is a Class A Vessel. */
public static final int TARGET_CLASSAVL = 0;
/* Indicates that the target is a Class B Vessel. */
public static final int TARGET_CLASSBVL = 1;
/* Indicates that the target is an AtoN. */
public static final int TARGET_AtoN = 2;
/* Indicates that the target is a base station. */
public static final int TARGET_BASESTN = 3;
/* Indicates that the target is a a SAR Aircraft. */
public static final int TARGET_SARACFT = 4;
/* The number of historical positions to store (A limit of
* 14440 equals an aprox 12 hours reporting every 3s).
*/
private static final int HISTORY_SIZE_LIMIT = 1700; //7220 = 6hrs
/* Entity Type - what type of AIS target's data is stored here... */
private int targetType;
/* The MMSI of this target. */
private String mmsi;
/* The target's name. */
private String name;
/* Last time this target was updated. */
private long lastUpdate;
/* Is this target considered lost by the system? */
private boolean lost;
/* Used to to store the previously reported position. */
private Position latestPosition;
/* The latest type 1,2 or 3 for class A, and type 18 for class B targets. */
private AISMsg latestDynamic;
/* The latest type 5 for class A targets, type 19 for class B, and used to store
* the only message for type 4 & 21 messages.
*/
private AISMsg latestMessage;
/* The ID of the base station with which the target was acquired most recently. */
private String rxID;
/* Indicates whether or not the target has just been discovered (ie is a new target. */
private boolean isNew;
/* The target's movement trail. */
private LinkedList<gov.nasa.worldwind.geom.Position> trail;
private Polyline visibleTrail;
/* The label that is visible to the user. */
private GlobeAnnotation label;
/* Denotes whether or not the target is currently being focused on by the user. */
private boolean focused;
/* A list of waypoints that define the target's predicted travel route.
* The amount of waypoints is defined by the time that the user wants to
* predict (e.g. a user may chose to predict the vessel's route 1 minute ahead
* of time or 3 minutes ahead of time etc).
*/
private List<Position> predictedRoute;
/* A list of stations that are currently picking up the target paired with
* a timestamp of when the target was last acquired by that station.
*/
private HashMap<String, Long> receiverRxIDList;
/* The target's width. */
private int beam;
/* The target's length. */
private int length;
/* The target's maximum draft. */
private double draft;
/* This is what's actually visible on NASA WorldWind. */
private SurfaceIcon visibleTarget;
/* The target's hull as specified through its AIS broadcast. */
private Polygon hull;
/* Indicates whether or not the target is missing target dimensions.
* If the target is using the default dimensions, then this is set to true;
* otherwise to false. True by default.
*/
private boolean missingTargetDimensions;
/* The distance between the target's GPS receiver and its bow. */
private int distanceToBow;
private int distanceToStern;
private int a;
private int b;
private int c;
private int d;
/* The safety bubble that is surrounding the target. Safety bubbles are
* virtual concepts that act to protect the target by assisting in collision
* detection.
*/
// private SafetyBubble safetyBubble;
/**
* Constructs a new <code>Target</code> object.
*
* @param msg The AIS message containing the information
* used to describe the target.
* @param rxID The rxID of the data source that identified the target
* i.e. that received its AIS message.
* @since 1.0
*/
public Target(AISMsg msg, String rxID) {
this.rxID = rxID;
this.receiverRxIDList = new HashMap<String, Long>();
receiverRxIDList.put(rxID, System.currentTimeMillis());
latestPosition = msg.getPosition();
isNew = true;
focused = false;
name = null;
missingTargetDimensions = true;
beam = 2;
length = 2;
distanceToBow = 2;
distanceToStern = 2;
a = b = c = d = 1;
// safetyBubble = new SafetyBubble(-1, null, length, beam);
hull = new Polygon();
predictedRoute = new ArrayList<Position>(200);
trail = new LinkedList<gov.nasa.worldwind.geom.Position>();
visibleTrail = new Polyline(trail);
visibleTrail.setColor(Color.WHITE);
visibleTrail.setLineWidth(2.0);
TargetLayer.getInstance().addRenderable(visibleTrail);
switch (msg.getMsgType()) {
case 1:
targetType = this.TARGET_CLASSAVL;
break;
case 2:
targetType = this.TARGET_CLASSAVL;
break;
case 3:
targetType = this.TARGET_CLASSAVL;
break;
case 4:
targetType = this.TARGET_BASESTN;
break;
case 5:
targetType = this.TARGET_CLASSAVL;
break;
case 9:
targetType = this.TARGET_SARACFT;
break;
case 18:
targetType = this.TARGET_CLASSBVL;
break;
case 19:
targetType = this.TARGET_CLASSBVL;
break;
case 21:
targetType = this.TARGET_AtoN;
}
lost = false;
mmsi = msg.getMMSI();
update(msg);
TargetLayer.getInstance().addRenderable(hull);
}
public void setFocused(boolean focused) {
this.focused = focused;
}
public boolean isFocused() {
return focused;
}
/**
* Removes the target from the <code>TargetLayer</code>
*
* @since 2.0
*/
public void detach() {
TargetLayer.getInstance().removeRenderable(visibleTrail);
TargetLayer.getInstance().removeRenderable(visibleTarget);
}
/**
* Updates the target's motion vector that defines its
* predicted route over time. This method is called every time
* that the target moves (i.e. for every change in position)
* @param minutes the minutes from now for which to predict the target's
* future route
* @param map The map used within the prediction context.
* @param worldObjects A list containing the <code>GeneralPath</code>
* of objects within the world. Objects are entities
* that are painted on the map e.g. country borders, boyes,
* landmarks, etc.
* @since 1.0
*/
public void predictRoute(int minutes) {
int heading = getHeading();
float speed = getSpeed();
if (speed < 0 || heading < 0 || heading == 511) {
return;
}
predictedRoute.clear();
// Convert speed to nautical miles per second
double nauticalMilesPerMinute = speed / 60.0;
predictedRoute.add(getPosition());
// Calculate the future position for every minute
for (int time = 1; time < minutes; time++) {
predictedRoute.add(NavCalculator.predictPosition(time, nauticalMilesPerMinute, getPosition().getLatitude(),
getPosition().getLongitude(), heading));
}
}
/**
* Returns the target's predicted route.
*
* @param minutes The time in the future for which to get the route
* (in minutes).
* @return The target's predicted route.
* @since 1.0
*/
public List<Position> getPredictedRoute(int time) {
predictRoute(time);
return predictedRoute;
}
/**
* Returns the (AIS) icon that represents the target. Icons are dependent on
* the target's current state and type. Refer to the software documentation for
* further details.
*
* @return <code>BufferedImage</code> object representing the target.
* @throws IOException
* @since 1.0
*/
private URL getIconURL() {
try {
AISMsg msg = (getLatestDynamicMsg() == null ? getLatestMessage()
: getLatestDynamicMsg());
String navStatus = msg.getNavStatus();
float speed = msg.getSpeed();
if (speed < 0) {
speed = 0;
}
int heading = msg.getHeading();
if (isLost()) {
return getClass().getResource("/assets/icons/ais/lost.png");
}
URL img = null;
if (navStatus.equals("at anchor")) {
if (speed > 0.2) {
if (heading < 0 || heading > 360) {
img = getClass().getResource("/assets/icons/ais/anchor-cog.png");
} else {
img = getClass().getResource("/assets/icons/ais/anchor-hdg.png");
}
} else {
img = getClass().getResource("/assets/icons/ais/anchor-000.png");
}
} else if (navStatus.equals("moored")) {
if (speed > 0.2) {
if (heading < 0 || heading > 360) {
img = getClass().getResource("/assets/icons/ais/moored-cog.png");
} else {
img = getClass().getResource("/assets/icons/ais/moored-hdg.png");
}
} else {
img = getClass().getResource("/assets/icons/ais/moored-000.png");
}
} else if (navStatus.equals("under way")) {
if (speed > 0.2) {
if (heading < 0 || heading > 360) {
img = getClass().getResource("/assets/icons/ais/uwy-cog.png");
} else {
img = getClass().getResource("/assets/icons/ais/uw-000.png");
}
} else {
img = getClass().getResource("/assets/icons/ais/uwy-drift.png");
}
} else if (navStatus.equals("not defined")) {
if (speed > 0.2) {
if (heading < 0 || heading > 360) {
img = getClass().getResource("/assets/icons/ais/notDef-cog.png");
} else {
img = getClass().getResource("/assets/icons/ais/notDef-hdg.png");
}
} else {
img = getClass().getResource("/assets/icons/ais/notDef-drift.png");
}
} else if (getType() == TARGET_AtoN || getMMSI().startsWith("99")) {
if (msg.getAidType().equals("Fixed structure")) {
img = getClass().getResource("/assets/icons/ais/AtoN-FM-V.png");
} else if (msg.getAidType().equals("Fixed structure")) {
img = getClass().getResource("/assets/icons/ais/AtoN-FM-V.png");
}
if (msg.getAidType().equals("Safe water mark")) {
img = getClass().getResource("/assets/icons/ais/AtoN-SWM.png");
} else {
img = getClass().getResource("/assets/icons/ais/baseStnAtoN.png");
}
} else if (getType() == TARGET_BASESTN) {
if (msg.getAidType().equals("Fixed structure")) {
img = getClass().getResource("/assets/icons/ais/AtoN-FM-V.png");
}
if (msg.getAidType().equals("Safe water mark")) {
img = getClass().getResource("/assets/icons/ais/AtoN-SWM.png");
} else {
img = getClass().getResource("/assets/icons/ais/baseStation.png");
}
} else if (getType() == TARGET_SARACFT) {
img = getClass().getResource("/assets/icons/ais/sar-aircraft.png");
}
return img;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Updates the target's AIS message to its most recently received one.
*
* @param latestMsg The most recently received AIS message.
* @since 1.0
*/
public void update(AISMsg latestMsg) {
// Update the message depending on its type (dynamic vs static)
if (latestMsg.getName() != null) {
name = latestMsg.getName();
}
switch (latestMsg.getMsgType()) {
case 1:
// Error check code for right messages type!
latestDynamic = latestMsg;
lastUpdate = latestDynamic.getUtcTimeStamp();
if (((AISMsg1_2_3) latestMsg).getNavStatus().compareTo("moored") != 0) {
updatePositionHistory(latestMsg.getPosition());
}
break;
case 2:
// Error check code for right messages type!
latestDynamic = latestMsg;
lastUpdate = latestDynamic.getUtcTimeStamp();
if (((AISMsg1_2_3) latestMsg).getNavStatus().compareTo("moored") != 0) {
updatePositionHistory(latestMsg.getPosition());
}
break;
case 3:
// Error check code for right messages type!
latestDynamic = latestMsg;
lastUpdate = latestDynamic.getUtcTimeStamp();
if (((AISMsg1_2_3) latestMsg).getNavStatus().compareTo("moored") != 0) {
updatePositionHistory(latestMsg.getPosition());
}
break;
case 4:
// Error check code for right messages type!
latestMessage = latestMsg;
lastUpdate = latestMessage.getUtcTimeStamp();
//no position history
break;
case 5:
// Error check code for right messages type!
latestMessage = latestMsg;
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MILLISECOND, -cal.get(Calendar.DST_OFFSET)
- cal.get(Calendar.ZONE_OFFSET));
lastUpdate = cal.getTimeInMillis();
// Calcukate vessek dimensions if they have not yet been set
AISMsg5 msg = (AISMsg5) latestMsg;
if (msg.getA() == 0 || msg.getB() == 0 || msg.getC() == 0
|| msg.getD() == 0) {
break;
}
a = msg.getA();
b = msg.getB();
c = msg.getC();
d = msg.getD();
beam = msg.getC() + msg.getD();
length = msg.getA() + msg.getB();
distanceToBow = length / 2;
distanceToStern = length / 2;
// safetyBubble.setLength(length);
// safetyBubble.setBeam(beam);
missingTargetDimensions = false;
break;
case 9:
// Error check code for right messages type!
latestDynamic = latestMsg;
lastUpdate = latestDynamic.getUtcTimeStamp();
updatePositionHistory(latestMsg.getPosition());
break;
case 18:
// Error check code for right messages type!
latestDynamic = latestMsg;
lastUpdate = latestDynamic.getUtcTimeStamp();
break;
case 19:
// Error check code for right messages type!
latestMessage = latestMsg;
lastUpdate = latestMessage.getUtcTimeStamp();
break;
case 21:
// Error check code for right messages type!
latestMessage = latestMsg;
if (latestMessage.getUtcTimeStampStr().compareTo("Not Available") == 0) {
cal = Calendar.getInstance();
cal.add(
Calendar.MILLISECOND, -cal.get(Calendar.DST_OFFSET)
- cal.get(Calendar.ZONE_OFFSET));
lastUpdate = cal.getTimeInMillis();
} else {
lastUpdate = latestMessage.getUtcTimeStamp();
}
break;
default:
}
lost = false;
// safetyBubble.setTargetPosition(getCentroid());
int heading = getHeading();
int course = getCourse();
// if (heading >= 0 && heading <= 360) {
// safetyBubble.setHeading(heading);
// } else if (course >= 0 && course <= 360) {
// safetyBubble.setHeading(course);
// }
Position pos = getPosition();
String txt = generateLabelText();
if (visibleTarget == null) {
visibleTarget = new SurfaceIcon(getIconURL(), LatLon.fromDegrees(pos.getLatitude(), pos.getLongitude()));
TargetLayer.getInstance().addRenderable(visibleTarget);
label = new GlobeAnnotation(txt, new gov.nasa.worldwind.geom.Position(LatLon.fromDegrees(pos.getLatitude(), pos.getLongitude()), 0.1));
TargetTextLayer.getInstance().addAnnotation(label);
} else {
visibleTarget.setImageSource(getIconURL());
visibleTarget.setLocation(LatLon.fromDegrees(pos.getLatitude(), pos.getLongitude()));
label.setText(txt);
label.setPosition(new gov.nasa.worldwind.geom.Position(LatLon.fromDegrees(pos.getLatitude(), pos.getLongitude()), 0.1));
}
visibleTarget.setHeading(Angle.fromDegrees(getCourse()));
}
/**
*
* @since 2.0
*/
private String generateLabelText() {
StringBuilder sb = new StringBuilder();
sb.append("<b>MMSI:</b> ");
sb.append(getMMSI());
sb.append("<br/>");
sb.append("<b>Dest.:</b> ");
sb.append(getDestination());
sb.append("<br/>");
sb.append("<b>Heading:</b> ");
sb.append(getHeading());
sb.append("<br/>");
sb.append("<b>Course:</b> ");
sb.append(getCourse());
sb.append("<br/>");
sb.append("<b>Speed:</b> ");
sb.append(getSpeed());
sb.append("<br/>");
sb.append("<b>Cargo:</b> ");
sb.append(getCargoType());
sb.append("<br/>");
sb.append("<b>Location:</b> ");
if (getPosition() != null) {
sb.append(getPosition().toStringDec());
} else {
sb.append("Unknown");
}
sb.append("<br/>");
return new String(sb);
}
/**
* Finds and returns the target's centre position.
*
* @return A <code>Position</code> representing the target's centre.
* @since 1.0
*/
public Position getCentroid() {
Position centre = null;
int direction = 0;
int heading = getHeading();
if (heading < 0 || heading > 360) {
heading = getCourse();
}
if (heading <= 270) {
direction = heading + 90;
} else {
direction = (heading + 90) - 360;
}
int dist = (length / 2) - b;
int dir0 = heading;
if (b > a) {
if (heading >= 180) {
dir0 = heading - 180;
} else {
dir0 = 360 + (heading - 180);
}
}
centre = NavCalculator.computePosition(getPosition(), dir0, dist);
centre = NavCalculator.computePosition(centre, direction, (beam / 2) - c);
return centre;
}
public int getA() {
return a;
}
public int getB() {
return b;
}
public int getC() {
return c;
}
public int getD() {
return d;
}
/**
* Updates the target's trail. A target's trail is a collection of
* its past waypoints.
*
* @param t The target's new trail.
* @since 1.0
*/
public void updateTrail(Trail t) {
if (trail.size() >= HISTORY_SIZE_LIMIT) {
for (int i = 0; i < 1000; i++) {
trail.remove(i);
}
}
trail.add(gov.nasa.worldwind.geom.Position.fromDegrees(t.getTrailPosition().getLatitude(), t.getTrailPosition().getLongitude(), 0));
visibleTrail.setPositions(trail);
}
public Position getSternPosition() {
// Calculate the bow's lat/long position if heading/course
// are available
Position sternPosition = null;
int heading = getHeading();
int course = getCourse();
if (heading < 0 || heading > 360) {
if (course >= 0 && course <= 360) {
heading = course;
} else {
return sternPosition;
}
}
if (heading <= 180) {
heading = heading + 180;
} else {
heading = (heading + 180) - 360;
}
sternPosition = NavCalculator.computePosition(getCentroid(),
heading, distanceToStern);
return sternPosition;
}
/**
* Returns the target's trail. A target's trail is a collection of
* its past waypoints.
*
* @return <code>List</code> object containing the target's
* trails.
* @since 1.0
*/
public List<gov.nasa.worldwind.geom.Position> getTrail() {
return trail;
}
/**
* Returns the target's navigational status.
*
* @return <code>String}</code> object representing the
* target's naviational status.
* @since 1.0
*/
public String getNavStatus() {
AISMsg msg = getLatestDynamicMsg();
if (msg == null) {
return "unknown";
} else if (isLost()) {
return "Lost";
}
return msg.getNavStatus();
}
/**
* Returns the vessel's color. Color is defined according to the
* visual perspectives in place.
*
* @return <code>Color</code> describing the vessel's color
* @since 2.0
*/
public static Color getColor(Target target) {
Color color = null;
AISMsg msg = (target.getLatestDynamicMsg() == null ? target.getLatestMessage()
: target.getLatestDynamicMsg());
String navStatus = msg.getNavStatus();
if (target.isLost()) {
return Color.RED;
}
if (navStatus.equals("at anchor")) {
color = new Color(14, 77, 120);
} else if (navStatus.equals("moored")) {
color = new Color(0, 231, 0);
} else if (navStatus.equals("under way")) {
color = Color.WHITE;
} else if (navStatus.equals("not defined")) {
color = new Color(162, 162, 162);
} else if (target.getType() == target.TARGET_AtoN || target.getMMSI().startsWith("99")) {
if (msg.getAidType().equals("Fixed structure")) {
color = Color.YELLOW;
}
if (msg.getAidType().equals("Safe water mark")) {
color = Color.RED;
} else {
color = Color.MAGENTA;
}
} else if (target.getType() == target.TARGET_BASESTN) {
if (msg.getAidType().equals("Fixed structure")) {
color = Color.YELLOW;
}
if (msg.getAidType().equals("Safe water mark")) {
color = new Color(205, 133, 0);
} else {
color = new Color(205, 173, 0);
}
}
return color;
}
/**
* Returns the number of seconds since the last update to this target.
*
* @return The number of seconds since the last
* received update.
* @since 1.0
*/
public long getElapsedTimeSinceLastUpdate() {
Calendar cal = Calendar.getInstance();
// Current system time in UTC
cal.add(Calendar.MILLISECOND, -cal.get(Calendar.DST_OFFSET)
- cal.get(Calendar.ZONE_OFFSET));
// Subtract last update time, converts to s & return
return (Math.round((cal.getTimeInMillis() - lastUpdate) / 1000));
}
/**
* Returns the MMSI for this target.
*
* @return <code>String</code> object representing the
* target's MMSI.
* @since 1.0
*/
public String getMMSI() {
return mmsi;
}
/**
* Indicates whether or not the target is missing dimensional information.
*
* @return <code>True</code> if the <code>Target</code> is
* missing dimensional information; <code>false</code>
* if not.
* @since 1.0
*/
public boolean isMissingDimensions() {
return missingTargetDimensions;
}
/**
* Returns the target's width in meters.
*
* @return The target's width in meters.
* @since 1.0
*/
public int getBeam() {
return beam;
}
/**
* Returns the target's length in meters.
*
* @return The target's length in meters.
* @since 1.0
*/
public int getLength() {
return length;
}
/**
* Returns the distance between the vessel's GPS antenna and
* its bow.
* @return The distance between the vessel's GPS antenna
* and its bow in meters.
* @since 1.0
*/
public int getDistanceToBow() {
return distanceToBow;
}
/**
* Returns the latest dynamic message that has been broadcasted by the target.
*
* @return <code>AISMsg</code> object representing the
* target's most recent dynamic message.
* @since 1.0
*/
public AISMsg getLatestDynamicMsg() {
return latestDynamic;
}
/**
* Returns the most recent AIS message.
*
* @return <code>AISMsg</code> the most recent AIS message.
* @since 1.0
*/
public AISMsg getLatestMessage() {
return latestMessage;
}
/**
* Returns the target's position.
*
* @return <code>Position</code> object representing the
* target's position.
* @since 1.0
*/
public Position getPosition() {
return latestPosition;
}
/**
* The time of the last update of this target.
* @return The time of the last update of this target.
* @since 1.0
*/
public long getTimeOfLastUpdate() {
//write code to compute time of last update!
return lastUpdate;
}
/**
* Removes all trails left by the target.
*
* @since 1.0
*/
public void clearTrail() {
for (int i = 0; i < trail.size(); i++) {
trail.remove(i);
}
}
/**
* Set this target as lost.
*
* @since 1.0
*/
public void setLost() {
lost = true;
}
/**
* Indicates whether or not the target has been lost.
*
* @return <code>True</code> if the target has
* been lost; <code>false</code> if not.
* @since 1.0
*/
public boolean isLost() {
return lost;
}
@Override
public void finalize() throws Throwable {
super.finalize();
}
/**
* Returns the type of target.
*
* @return <code>Integer</code> flag indicating the target type.
* @since 1.0
*/
public int getType() {
return targetType;
}
/**
* Returns the vessel's draft in meters.
*
* @return The vessel's draft (in meters).
* @since 1.0
*/
public float getDraft() {
return (latestMessage != null ? latestMessage.getMaxDraft() : -1);
}
/**
* Returns the position of the target's bow.
*
* @return The <code>Position</code> of the target's bow.
* @since 1.0
*/
public Position getBowPosition() {
// Calculate the bow's lat/long position if heading/course
// are available
Position bowPosition = null;
int heading = getHeading();
int course = getCourse();
if (heading < 0 || heading > 360) {
if (course >= 0 && course <= 360) {
bowPosition = NavCalculator.computePosition(getCentroid(),
course, distanceToBow * 0.7);
}
} else {
bowPosition = NavCalculator.computePosition(getCentroid(),
heading, distanceToBow * 0.7);
}
return bowPosition;
}
/**
* Returns the position of the target's port.
*
* @return portPosition The position of the target's port.
* @since 1.0
*/
public Position getPortPosition() {
// Calculate the port's lat/long position if heading/course
// are available
Position portPosition = null;
int heading = getHeading();
int course = getCourse();
if (heading < 0) {
if (course >= 0) {
heading = course;
}
}
if (heading < 0 || heading > 360) {
return portPosition;
}
if (heading >= 90) {
portPosition = NavCalculator.computePosition(getCentroid(), heading - 90, c);
} else if (heading < 90) {
int portHeading = heading - 90;
portHeading = 360 + portHeading;
portPosition = NavCalculator.computePosition(getCentroid(), portHeading, c);
}
return portPosition;
}
/**
* Returns the position of the target's starboard.
*
* @return starboardPosition The position of the target's starboard.
* @since 1.0
*/
public Position getStarboardPosition() {
// Calculate the port's lat/long position if heading/course
// are available
Position starboardPosition = null;
int heading = getHeading();
int course = getCourse();
if (heading < 0) {
if (course >= 0 && course != 511) {
heading = course;
}
}
if (heading < 0 || heading > 360) {
return starboardPosition;
}
if (heading <= 270) {
starboardPosition = NavCalculator.computePosition(getCentroid(), heading + 90, d);
} else {
int starboardHeading = heading + 90;
starboardHeading = starboardHeading - 360;
starboardPosition = NavCalculator.computePosition(getCentroid(), starboardHeading, d);
}
return starboardPosition;
}
/**
* Returns an instance of the target's safety bubble.
*
* @return safetyBubble The target's <code>SafetyBubble</code>
* @see SafetyBubble
* @since 1.0
*/
// public SafetyBubble getSafetyBubble() {
// return safetyBubble;
// }
/**
* The RxID of the station that last received this target.
*
* @return rxID The ID of the receiver that last received the target.
* @since 1.0
*/
public String getSourceRxID() {
return rxID;
}
/**
* Returns the target type in string format.
*
* @return <code>String</code> object indicating the target type.
* @since 1.0
*/
public String getTypeStr() {
switch (targetType) {
case TARGET_CLASSAVL:
return "CLASSAVL";
case TARGET_BASESTN:
return "BASE STATION";
case TARGET_SARACFT:
return "SARACFT";
case TARGET_CLASSBVL:
return "CLASSBVL";
case TARGET_AtoN:
return "AtoN";
}
return "unknow";
}
/**
* Returns the target's speed in nautical miles per hour.
*
* @return The target's speed.
* @since 1.0
*/
public float getSpeed() {
AISMsg msg = getLatestDynamicMsg();
if (msg == null) {
return AISMsg.UNKNOWN;
}
return msg.getSpeed();
}
/**
* Updates the target's position history.
*
* @param <code>Position</code> to update to.
* @since 1.0
*/
private void updatePositionHistory(Position position) {
if (latestPosition != null) {
isNew = false;
}
if (position == null || latestPosition == null) {
latestPosition = position;
return;
}
// If the two positions are different, then add the current latest
// position to the trail and replace it with the new position
if (position.getLatitude() != latestPosition.getLatitude()
|| position.getLongitude() != latestPosition.getLongitude()) {
try {
updateTrail(new Trail(new Position(latestPosition.getLatitude(),
latestPosition.getLongitude())));
drawHull();
} catch (Exception e) {
e.printStackTrace();
}
}
latestPosition = position;
}
/**
* Draws the target's hull.
*
* @since 2.0
*/
private void drawHull() {
Color color;
if (isMissingDimensions()) {
color = Color.DARK_GRAY;
return;
} else {
color = Color.BLACK;
}
Position bow = getBowPosition();
Position stern = getSternPosition();
int heading = getHeading();
if (heading < 0 || heading > 360) {
heading = getCourse();
if (heading < 0 || heading > 360) {
return;
}
}
double dist = getDistanceToBow();
dist = dist * 0.3;
Position bowTip = NavCalculator.computePosition(bow, heading, dist);
int direction;
dist = getBeam() / 2;
if (heading >= 90) {
direction = heading - 90;
} else {
direction = (heading - 90) + 360;
}
Position bowPort = NavCalculator.computePosition(bow, direction, dist);
Position sternPort = NavCalculator.computePosition(stern, direction, dist);
if (heading <= 270) {
direction = heading + 90;
} else {
direction = (heading + 90) - 360;
}
Position sternStarboard = NavCalculator.computePosition(stern, direction, dist);
Position bowStarboard = NavCalculator.computePosition(bow, direction, dist);
double elevation = visibleTrail.getPositions().iterator().next().getElevation();
List<gov.nasa.worldwind.geom.Position> boundaries = new ArrayList<gov.nasa.worldwind.geom.Position>();
boundaries.add(new gov.nasa.worldwind.geom.Position(LatLon.fromDegrees(bowPort.getLatitude(), bowPort.getLongitude()), elevation));
boundaries.add(new gov.nasa.worldwind.geom.Position(LatLon.fromDegrees(sternPort.getLatitude(), sternPort.getLongitude()), elevation));
boundaries.add(new gov.nasa.worldwind.geom.Position(LatLon.fromDegrees(sternStarboard.getLatitude(), sternStarboard.getLongitude()), elevation));
boundaries.add(new gov.nasa.worldwind.geom.Position(LatLon.fromDegrees(bowStarboard.getLatitude(), bowStarboard.getLongitude()), elevation));
boundaries.add(new gov.nasa.worldwind.geom.Position(LatLon.fromDegrees(bowTip.getLatitude(), bowTip.getLongitude()), elevation));
// boundaries.add(new gov.nasa.worldwind.geom.Position(LatLon.fromDegrees(getPosition().getLatitude(), getPosition().getLongitude()), elevation));
// boundaries.add(new gov.nasa.worldwind.geom.Position(LatLon.fromDegrees(getCentroid().getLatitude(), getCentroid().getLongitude()), elevation));
hull.setOuterBoundary(boundaries);
// g.draw(poly);
// g.drawString("x", (int) p6.getX(), (int) p6.getY());
// g.fillOval((int) p7.getX(), (int) p7.getY(), 2, 2);
// if (target.isMissingDimensions()) {
// Point p8 = map.toPixel(bow);
// g.drawString("<No Dim>", (int) p8.getX() + 10,
// (int) p8.getY() + 10);
// }
// g.setColor(color);
}
/**
* Indicates whether or not the target has just been received.
*
* @return <code>True</code>} if the target is new;
* <code>false</code> if not.
* @since 1.0
*/
public boolean isNew() {
return isNew;
}
/**
* Updates the rxID of the station receiving the target.
*
* @param rxID The new rxID of the source station.
* @since 1.0
*/
public void updateSourceRxID(String rxID) {
this.rxID = rxID;
// Add the receiver's rxID to the list if it does not exist already
// otherwise just update its timestamp
receiverRxIDList.put(rxID, System.currentTimeMillis());
}
/**
* Returns the target's name.
* @return <code>String</code> containing the target's name.
* @since 1.0
*/
public String getName() {
if (name == null) {
return "?";
}
return name;
}
/**
* Returns a list of stations that are currently receiving this target.
*
* @return <code>List</code> of stations that currently receive this target.
* @since 1.0
*/
public HashMap<String, Long> getSourceRxIDList() {
return receiverRxIDList;
}
/**
* Returns the target's course in degrees (from 0 to 359).
*
* @return The target's course.
* @since 1.0
*/
public int getCourse() {
AISMsg msg = getLatestDynamicMsg();
int course;
if (msg == null) {
return AISMsg.UNKNOWN;
}
course = msg.getCourse();
if (course == 511 || course < 0) {
return AISMsg.UNKNOWN;
}
String strCourse = "" + course;
if (strCourse.length() < 3) {
for (int i = strCourse.length(); i < 3; i++) {
strCourse = "0" + strCourse.length();
}
course = Integer.parseInt(strCourse);
}
return course;
}
/**
* Returns the target's course.
*
* @return The target's course
* @since 1.0
*/
public int getHeading() {
AISMsg msg = getLatestDynamicMsg();
int heading;
String strHeading;
if (msg == null) {
return AISMsg.UNKNOWN;
}
heading = msg.getHeading();
if (heading == 511 || heading < 0) {
return AISMsg.UNKNOWN;
}
strHeading = "" + heading;
if (strHeading.length() < 3) {
for (int i = strHeading.length(); i < 3; i++) {
strHeading = "0" + strHeading;
}
heading = Integer.parseInt(strHeading);
}
return heading;
}
/**
* Returns the type of cargo loaded by the target.
*
* @return The type of cargo loaded by the target.
* @since 1.0
*/
public String getCargoType() {
AISMsg msg = getLatestMessage();
if (msg == null) {
return "?";
}
return msg.getCargoType();
}
/**
* Returns the target's destination.
*
* @return The target's destination.
* @since 1.0
*/
public String getDestination() {
AISMsg msg = getLatestMessage();
if (msg == null) {
return "?";
}
return msg.getDestination();
}
/**
* Returns the target's Exptected Time of Arrival (ETA).
*
* @return The target's ETA.
* @since 1.0
*/
public String getEta() {
AISMsg msg = getLatestMessage();
if (msg == null) {
return "?";
}
return msg.getEta();
}
/**
* Returns a string containing all target information.
*
* @return Target information.
* @since 1.0
*/
public String print() {
String info = "";
info += "Name: " + getName() + "\n";
info += "Destination: " + getDestination() + "\n";
info += "ETA: " + getEta() + "\n";
info += "\nMMSI: " + getMMSI() + "\n";
info += "Heading: " + getHeading() + " (" + getCourse() + "(c))\n";
info += "Speed: " + getSpeed() + "\n";
info += "Cargo Type: " + getCargoType() + "\n";
info += "LOA: " + getLength() + " Beam: " + getBeam() + " Draft:";
info += "\nLast received by: " + getSourceRxID() + "\n";
return info;
}
}