/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package jaid.ais;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.render.SurfaceIcon;
import jaid.ais.data.ActiveTargetsMap;
import jaid.ais.data.Target;
import jaid.ais.message.AISMsg;
import jaid.com.Client;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import nav.position.Position;
import nav.util.math.NavCalculator;
import ui.layers.TargetLayer;
/**
* This class represents the notion of an AIS receiver.
*
* The class also defines an anonymous class for wrapping coverage permiter data.
*
* @author Benjamin Jakobus
* @since 1.0
* @version 1.0
*/
public class DataSource {
/* The AIS receiver's position. */
private Position location;
/* The receiver's rxID. */
private String rxID;
/* The IP address of the server that forwards the receiver's messages. */
private String ipAddress;
/* The port on which the server that forwards the receiver's messages runs. */
private int port;
/* Flag indicating whether or not the receiver is enabled (data from disabled
* receivers will be discarded).
*/
private boolean isEnabled;
/* The color of the receiver's coverage polygon. */
private Color color;
/* The client used to connect to the AIS server. */
private Client client;
/* Contains positions for the base station's coverage. */
private Map<Double, TableEntry> distanceMap;
/* Contains all targets received. */
private ActiveTargetsMap targetsMap;
/* A list of positions that define the source's coverage. */
private List<Position> coveragePerimeter;
/* The icon denoting the station's location on the map. */
private SurfaceIcon icon;
/**
* Constructs a new <code>AISSource</code> object.
*
* @param ipAddress The IP Address of the remote server that
* provides the AIS data.
* @param port The port on which the remote server listens to.
* @param rxID The remote server's rxID. The rxID string is
* used to identify the individual AIS source.
* @param latitude The latitude coordinate of the AIS source.
* @param longitude The longitude coordinate of the AIS source.
* @param color The color that is to be associated with the
* source's coverage polygon.
* @since 1.0
*/
public DataSource(final Position location, String rxID, String ipAddress, int port,
boolean isEnabled, Color color, ActiveTargetsMap targetsMap) {
this.location = location;
this.rxID = rxID;
this.ipAddress = ipAddress;
this.port = port;
this.targetsMap = targetsMap;
this.isEnabled = isEnabled;
this.color = color;
client = new Client(port, ipAddress, targetsMap, rxID);
distanceMap = initDistanceMap(new ConcurrentHashMap<Double, TableEntry>(3600));
Thread coverageCalculatorThread = new Thread(new Runnable() {
public void run() {
List<Double> tmpList;
Position pos;
while (true) {
try {
coveragePerimeter = new ArrayList<Position>(3600);
coveragePerimeter.add(location);
calculateCoveragePerimiter();
tmpList = new ArrayList<Double>(3600);
// Add any non-null positions to the geo-position list so
// that they can later form the points for the coverage polygon
for (Double key : distanceMap.keySet()) {
if (distanceMap.get(key).getPosition() != null) {
tmpList.add(key);
}
}
// Sort the positions according to their appearance from 0 to 360 degrees
Collections.sort(tmpList);
for (Double key : tmpList) {
pos = new Position(distanceMap.get(key).getPosition().getLatitude(),
distanceMap.get(key).getPosition().getLongitude());
coveragePerimeter.add(pos);
}
pos = new Position(getLocation().getLatitude(),
getLocation().getLongitude());
coveragePerimeter.add(pos);
// Sleep two seconds
pos = null;
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
// Re-calculate the coverage perimiter every 30 seconds
coverageCalculatorThread.start();
icon = new SurfaceIcon(getClass().getResource("/assets/icons/ais/antenna.png"), LatLon.fromDegrees(getLocation().getLatitude(), getLocation().getLongitude()));
TargetLayer.getInstance().addRenderable(icon);
}
/**
* Returns the color of the coverage polygon.
*
* @return color The color of the coverage polygon.
* @since 1.0
*/
public Color getColor() {
return color;
}
/**
* Returns the server's IP address.
*
* @return IP The IP address of the server that broadcasts the AIS feeds.
* @since 1.0
*/
public String getIpAddress() {
return ipAddress;
}
/**
* Enables / disables the receiver.
*
* @param isEnabled <code>True</code> if the receiver is to be
* enabled; <code>false</code> if not.
* @since 1.0
*/
public void setEnabled(boolean isEnabled) {
if (!isEnabled) {
disconnect();
}
this.isEnabled = isEnabled;
}
/**
* Determines whether or not the receiver is enabled.
*
* @return <code>True</code> if the receiver is enabled;
* else <code>false</code>
* @since 1.0
*/
public boolean isEnabled() {
return isEnabled;
}
/**
* Returns the location of the receiver.
*
* @return <code>Position</code> object denoting the receiver's
* position
* @since 1.0
*/
public Position getLocation() {
return location;
}
/**
* Returns the port number on which the server listens.
*
* @return port The port number on which the server listens.
* @since 1.0
*/
public int getPort() {
return port;
}
/**
* Returns the receiver's rxID.
*
* @return rxID The receiver's rxID.
* @since 1.0
*/
public String getRxID() {
return rxID;
}
/**
* Sets the IP address to which to connect.
*
* @param ipAddress Server IP Address.
* @since 1.0
*/
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
/**
* Returns the <code>Target</code> with the given MMSI.
*
* @param mmsi
* @return
* @since 2.0
*/
public Target getTargetByMMSI(String mmsi) {
return targetsMap.get(mmsi);
}
/**
* Returns the <code>Target</code> with the given name.
*
* @param name
* @return
* @since 2.0
*/
public Target getTargetByName(String name) {
for (Target t : targetsMap.values()) {
if (t.getName().contains(name)) {
return t;
}
}
return null;
}
/**
* Sets the receiver's location.
*
* @param location <code>Position</code> denoting the receiver's
* destination.
* @since 1.0
*/
public void setLocation(Position location) {
this.location = location;
}
/**
* Sets the port on which to connect.
*
* @param port Port on which to connect.
* @since 1.0
*/
public void setPort(int port) {
this.port = port;
}
/**
* Sets the rxID of the receiver.
*
* @param rxID Receiver rxID.
* @since 1.0
*/
public void setRxID(String rxID) {
this.rxID = rxID;
}
/**
* Returns all targets identified by an individual source.
*
* @param rxID The rxID of the source whose identified targets
* we want to fetch.
* @return A collection of targets identified by the source
* matching rxID.
* @since 1.0
*/
public Collection<Target> getTargets() {
Collection<Target> allTargets = targetsMap.values();
Collection<Target> targets = new ArrayList<Target>();
for (Target t : allTargets) {
if (rxID.equals(t.getSourceRxID())) {
targets.add(t);
}
}
return targets;
}
/**
* Returns the source's client.
*
* @return The client used by the source to obtain data feeds.
* @since 1.0
*/
public Client getClient() {
return client;
}
/**
* Returns the source's coverage.
*
* @return <code>List</code> containg all the waypoints
* defining the source's coverage.
* @since 1.0
*/
public List<Position> getCoverage() {
return coveragePerimeter;
}
/**
* Creates a new distances map used for calculating the
* perimeter around an AIS source.
*
* @param map The distance map which to initialize.
* @return <code>Map</code> containing perimeter data.
* @since 1.0
*/
private Map initDistanceMap(Map map) {
map.clear();
for (int i = 0; i < 360; i++) {
for (double j = 0; j < 10; j += 0.1) {
double key = i + j;
map.put(key, new TableEntry(0, null));
}
}
return map;
}
public void start() {
client.start();
}
/**
* Calculates the perimeter to be drawn around each AIS base station.
*
* @param feed The AIS feed for which to calculate the coverage area.
* @since 1.0
*/
private void calculateCoveragePerimiter() {
NavCalculator nav = new NavCalculator();
AISMsg msg;
// Calculate the distances from each target to the base station
double distance;
Position targetPosition;
double key;
for (Target t : getTargets()) {
if (t.getType() == Target.TARGET_BASESTN) {
continue;
}
targetPosition = t.getPosition();
if (targetPosition == null) {
if (targetPosition == null) {
msg = (t.getLatestDynamicMsg() == null
? t.getLatestMessage()
: t.getLatestDynamicMsg());
if (msg.getPosition() != null) {
targetPosition = msg.getPosition();
} else {
continue;
}
}
}
// Filter: Delete targets that have badly configured positions
if (targetPosition.getLatitude() <= 0 || targetPosition.getLongitude() <= -50) {
targetsMap.remove(t.getMMSI());
continue;
}
// Calculate the vessel's distance from the AIS receiver
nav.mercatorSailing(getLocation(), targetPosition);
distance = nav.getDistance();
// Remove targets that are too far away and that are hence the result of badly configured
// receivers
if (distance > 300) {
continue;
}
key = Math.round(nav.getTrueCourse());
if (distanceMap.get(key) == null) {
continue;
}
if (distanceMap.get(key).getDistance() < distance) {
distanceMap.remove(key);
distanceMap.put(key, new TableEntry(distance, targetPosition));
}
}
}
/**
* Disconnects the source from its server, causing it to
* no longer receive AIS feeds.
*
* @since 1.0
*/
private void disconnect() {
client.disconnect();
}
/**
* Wrapper class used when calculating the coverage perimiter.
* The class maps a distance to a position, therefore allowing
* us to retrieve the distance between any position and the position
* of the AIS receiver.
*
* @since 1.0
*/
private class TableEntry {
private double distance;
private Position p;
/**
* Constructor
* @param distance
* @param p
* @since 1.0
*/
public TableEntry(double distance, Position p) {
this.distance = distance;
this.p = p;
}
/**
* Getter method for the distance between the position and the
* AIS receiver.
*
* @return distance
* @since 1.0
*/
public double getDistance() {
return distance;
}
/**
* Getter method for the position mapped to the distance.
*
* @return position
* @since 1.0
*/
public Position getPosition() {
return p;
}
}
}