dk.dma.epd.ship.service.IntendedRouteHandler.java Source code

Java tutorial

Introduction

Here is the source code for dk.dma.epd.ship.service.IntendedRouteHandler.java

Source

/* Copyright (c) 2011 Danish Maritime Authority.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dk.dma.epd.ship.service;

import java.util.Iterator;
import java.util.Map.Entry;

import net.maritimecloud.net.mms.MmsClient;

import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import dk.dma.epd.common.prototype.model.intendedroute.FilteredIntendedRoute;
import dk.dma.epd.common.prototype.model.intendedroute.FilteredIntendedRoutes;
import dk.dma.epd.common.prototype.model.intendedroute.IntendedRouteFilterMessage;
import dk.dma.epd.common.prototype.model.route.ActiveRoute;
import dk.dma.epd.common.prototype.model.route.IRoutesUpdateListener;
import dk.dma.epd.common.prototype.model.route.IntendedRoute;
import dk.dma.epd.common.prototype.model.route.PartialRouteFilter;
import dk.dma.epd.common.prototype.model.route.Route;
import dk.dma.epd.common.prototype.model.route.RoutesUpdateEvent;
import dk.dma.epd.common.prototype.service.IntendedRouteHandlerCommon;
import dk.dma.epd.common.prototype.settings.EnavSettings;
import dk.dma.epd.common.text.Formatter;
import dk.dma.epd.common.util.Converter;
import dk.dma.epd.common.util.Util;
import dk.dma.epd.ship.EPDShip;
import dk.dma.epd.ship.layers.intendedroute.IntendedRouteLayer;
import dk.dma.epd.ship.route.RouteManager;
import dk.dma.epd.ship.settings.handlers.IIntendedRouteHandlerSettingsObserver;
import dma.route.IntendedRouteBroadcast;

/**
 * Ship specific intended route service implementation.
 * <p>
 * Listens for changes to the active route and broadcasts it. Also broadcasts the route periodically.
 * <p>
 * Improvements:
 * <ul>
 * <li>Use a worker pool rather than spawning a new thread for each broadcast.</li>
 * </ul>
 */
public class IntendedRouteHandler extends IntendedRouteHandlerCommon
        implements IRoutesUpdateListener, Runnable, IIntendedRouteHandlerSettingsObserver {

    private static final Logger LOG = LoggerFactory.getLogger(IntendedRouteHandler.class);
    private static long BROADCAST_TIME = 60; // Broadcast intended route every
                                             // minute for now
    private static long ADAPTIVE_TIME = 60 * 10; // Set to 10 minutes?
    private static final int BROADCAST_RADIUS = Integer.MAX_VALUE;

    private DateTime lastTransmitActiveWp;
    private DateTime lastSend = new DateTime(1);
    private RouteManager routeManager;
    private boolean running;

    private IntendedRouteLayer intendedRouteLayer;

    /**
     * Constructor
     */
    public IntendedRouteHandler() {
        super();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void cloudConnected(MmsClient connection) {
        // Let super hook up for intended route broadcasts from other vessels
        super.cloudConnected(connection);

        // Start broadcasting our own active route
        running = true;
        new Thread(this).start();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void cloudDisconnected() {
        running = false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void shutdown() {
        // Before shutting down, attempt to broadcast a no-intended route
        // message
        // so that other clients will remove the intended route of this ship
        broadcastIntendedRoute(null, false);

        super.shutdown();
    }

    /**
     * Main thread run method. Broadcasts the intended route
     */
    public void run() {

        // Initialize first send
        // lastSend = new DateTime();
        // broadcastIntendedRoute();

        while (running) {

            if (routeManager != null) {

                // We have no active route, keep sleeping
                if (routeManager.getActiveRoute() == null) {
                    Util.sleep(BROADCAST_TIME * 1000L);
                } else {

                    // Here we handle the periodical broadcasts
                    DateTime calculatedTimeOfLastSend = new DateTime();
                    calculatedTimeOfLastSend = calculatedTimeOfLastSend.minus(BROADCAST_TIME * 1000L);

                    // Do we need to rebroadcast based on the broadcast time
                    // setting
                    if (calculatedTimeOfLastSend.isAfter(lastSend)) {
                        LOG.debug("Periodically rebroadcasting");
                        broadcastIntendedRoute();
                        lastSend = new DateTime();
                    } else if (lastTransmitActiveWp != null) {

                        // We check for the adaptive route broadcast here
                        // We need to compare lastTransmitActiveWp which is the
                        // last stored
                        // ETA of the waypoint we sent to the current one
                        DateTime currentActiveWaypointETA = new DateTime(
                                routeManager.getActiveRoute().getActiveWaypointEta());

                        // LOG.debug("The ETA at last transmission was : " +
                        // lastTransmitActiveWp);
                        // LOG.debug("It is now                        : " +
                        // currentActiveWaypointETA);

                        // //It can either be before or after
                        //
                        if (currentActiveWaypointETA.isAfter(lastTransmitActiveWp)
                                || currentActiveWaypointETA.isBefore(lastTransmitActiveWp)) {

                            long etaTimeChange;

                            // Is it before?
                            if (currentActiveWaypointETA.isAfter(lastTransmitActiveWp)) {

                                etaTimeChange = currentActiveWaypointETA.minus(lastTransmitActiveWp.getMillis())
                                        .getMillis();

                                // Must be after
                            } else {
                                etaTimeChange = currentActiveWaypointETA.plus(lastTransmitActiveWp.getMillis())
                                        .getMillis();
                            }

                            if (etaTimeChange > ADAPTIVE_TIME * 1000L) {
                                LOG.debug("Broadcast based on adaptive time!");
                                broadcastIntendedRoute();
                                lastSend = new DateTime();
                            }

                            // LOG.debug("ETA has changed with " + etaTimeChange
                            // + " mili seconds" );

                        }

                    }

                    Util.sleep(1000L);

                }
            }

        }

    }

    /**
     * Broadcast intended route
     */
    public void broadcastIntendedRoute() {
        broadcastIntendedRoute(routeManager.getActiveRoute(), true);
    }

    /**
     * Broadcast intended route
     * 
     * @param activeRoute
     *            the active route to broadcast
     * @param async
     *            whether to broadcast the message asynchronously or not
     */
    public void broadcastIntendedRoute(ActiveRoute activeRoute, boolean async) {
        // Sanity check
        if (!running || routeManager == null || getMmsClient() == null) {
            return;
        }

        // Make intended route message
        IntendedRouteBroadcast message = new IntendedRouteBroadcast();

        if (activeRoute != null) {
            PartialRouteFilter filter = EPDShip.getInstance().getSettings().getCloudSettings()
                    .getIntendedRouteFilter();
            message = activeRoute.getPartialRouteData(filter);

            lastTransmitActiveWp = new DateTime(activeRoute.getActiveWaypointEta());

        } else {
            message.setRoute(new dma.route.Route());
        }

        // send message
        LOG.debug("Broadcasting intended route");
        final IntendedRouteBroadcast broadcast = message;
        Runnable broadcastMessage = new Runnable() {
            @Override
            public void run() {
                getMmsClient().broadcast(broadcast);
            }
        };

        if (async) {
            submitIfConnected(broadcastMessage);
        } else {
            broadcastMessage.run();
        }
    }

    /**
     * Handle event of active route change
     */
    @Override
    public void routesChanged(RoutesUpdateEvent e) {
        if (e != null) {
            if (e.is(RoutesUpdateEvent.ACTIVE_ROUTE_UPDATE, RoutesUpdateEvent.ACTIVE_ROUTE_FINISHED,
                    RoutesUpdateEvent.ROUTE_ACTIVATED, RoutesUpdateEvent.ROUTE_DEACTIVATED)) {

                lastSend = new DateTime();
                broadcastIntendedRoute();

                updateFilter();

            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void findAndInit(Object obj) {
        super.findAndInit(obj);

        if (obj instanceof RouteManager) {
            routeManager = (RouteManager) obj;
            routeManager.addListener(this);
        } else if (obj instanceof IntendedRouteLayer) {
            intendedRouteLayer = (IntendedRouteLayer) obj;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void findAndUndo(Object obj) {
        if (obj instanceof RouteManager) {
            routeManager.removeListener(this);
            routeManager = null;
        }
        super.findAndUndo(obj);
    }

    /****************************************/
    /** Intended route filtering **/
    /****************************************/

    /**
     * {@inheritDoc}
     */
    @Override
    protected String formatNotificationDescription(FilteredIntendedRoute filteredIntendedRoute) {

        Long otherMmsi = (filteredIntendedRoute.getMmsi1().equals(getOwnShipMmsi()))
                ? filteredIntendedRoute.getMmsi2()
                : filteredIntendedRoute.getMmsi1();

        IntendedRouteFilterMessage msg = filteredIntendedRoute.getMinimumDistanceMessage();

        String actor = "MMSI " + otherMmsi;
        if (EPDShip.getInstance().getIdentityHandler().getActor(otherMmsi) != null) {
            actor = EPDShip.getInstance().getIdentityHandler().getActor(otherMmsi).getName();
        }

        return String.format("Your active route comes within %s of vessel %s at %s.",
                Formatter.formatDistNM(Converter.metersToNm(msg.getDistance())), actor,
                Formatter.formatYodaTime(msg.getTime1()));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Long getMmsi(Route route) {
        if (route instanceof IntendedRoute) {
            return ((IntendedRoute) route).getMmsi();
        }

        // Must be the active route. Return own-ship MMSI
        return getOwnShipMmsi();
    }

    /**
     * Returns the own-ship MMSI or -1 if undefined
     * 
     * @return the own-ship MMSI
     */
    public Long getOwnShipMmsi() {
        Long ownShipMmsi = EPDShip.getInstance().getMmsi();
        return (ownShipMmsi != null) ? ownShipMmsi : -1L;
    }

    /**
     * Update all filters
     */
    @Override
    protected void updateFilter() {

        // Recalculate everything
        // Compare all routes to current active route

        FilteredIntendedRoutes filteredIntendedRoutes = new FilteredIntendedRoutes();

        // Compare all intended routes against our own active route

        if (routeManager.getActiveRoute() != null) {

            // The route we're comparing against
            ActiveRoute activeRoute = routeManager.getActiveRoute();

            Iterator<Entry<Long, IntendedRoute>> it = intendedRoutes.entrySet().iterator();
            while (it.hasNext()) {
                Entry<Long, IntendedRoute> intendedRoute = it.next();

                IntendedRoute recievedRoute = intendedRoute.getValue();

                FilteredIntendedRoute filter = findTCPA(activeRoute, recievedRoute);
                // Try other way around
                if (!filter.include()) {
                    filter = findTCPA(recievedRoute, activeRoute);
                }

                // No warnings, ignore it
                if (filter.include()) {

                    // Add the filtered route to the list
                    filteredIntendedRoutes.add(filter);

                }

            }

            // Call an update
            intendedRouteLayer.loadIntendedRoutes();

        }

        // Check if we need to raise any alerts
        checkGenerateNotifications(this.filteredIntendedRoutes, filteredIntendedRoutes);

        // Override the old set of filtered intended route
        this.filteredIntendedRoutes = filteredIntendedRoutes;
    }

    /**
     * Update filter with new intended route
     * 
     * @param route
     */
    @Override
    protected void applyFilter(IntendedRoute route) {
        // If previous intended route exist re-apply filter

        if (routeManager.getActiveRoute() != null) {

            FilteredIntendedRoute filter = findTCPA(routeManager.getActiveRoute(), route);

            // Try other way around or dont
            if (!filter.include()) {
                filter = findTCPA(route, routeManager.getActiveRoute());
            }

            // No warnings, ignore it
            if (!filter.include()) {

                // Remove it, if it exists
                if (this.filteredIntendedRoutes.containsKey(route.getMmsi())) {
                    filteredIntendedRoutes.remove(route.getMmsi());
                    LOG.debug("Remove from filter as it is no longer valid");
                }

            } else {
                // Check if we should generate notification
                checkGenerateNotifications(filteredIntendedRoutes, filter);

                // Add the filtered route to the list
                filteredIntendedRoutes.add(filter);

            }
        }
    }

    @Override
    public void sendIntendedRouteChanged(boolean value) {
        // TODO Auto-generated method stub

    }

    @Override
    public void broadcastTimeChanged(long value) {
        BROADCAST_TIME = value;
    }

    @Override
    public void adaptiveBroadcastTimeChanged(int value) {
        // TODO Auto-generated method stub
        ADAPTIVE_TIME = value;
    }

    /**
     * Updates settings by invoking super implementation. Additionally calls {@link #updateFilter()} to refresh the filter according
     * to the updated settings.
     */
    @Override
    public void updateSettings(EnavSettings settings) {
        super.updateSettings(settings);
        if (this.routeManager != null) {
            // Reapply filter with updated settings.
            this.updateFilter();
            /*
             * Fire dummy event such that any listening IntendedRouteTCPALayer will redraw TCPAs.
             */
            this.fireIntendedEvent(null);

        }
    }

}