dk.dma.epd.ship.EPDShip.java Source code

Java tutorial

Introduction

Here is the source code for dk.dma.epd.ship.EPDShip.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;

import java.awt.event.ActionEvent;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import net.maritimecloud.core.id.MaritimeId;
import net.maritimecloud.core.id.MmsiId;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.bbn.openmap.MapHandler;
import com.jtattoo.plaf.hifi.HiFiLookAndFeel;

import dk.dma.ais.virtualnet.transponder.gui.TransponderFrame;
import dk.dma.commons.app.OneInstanceGuard;
import dk.dma.enav.model.geometry.Position;
import dk.dma.epd.common.ExceptionHandler;
import dk.dma.epd.common.graphics.Resources;
import dk.dma.epd.common.prototype.Bootstrap;
import dk.dma.epd.common.prototype.EPD;
import dk.dma.epd.common.prototype.PluginLoader;
import dk.dma.epd.common.prototype.ais.VesselTarget;
import dk.dma.epd.common.prototype.gui.SystemTrayCommon;
import dk.dma.epd.common.prototype.model.identity.IdentityHandler;
import dk.dma.epd.common.prototype.model.voyage.VoyageEventDispatcher;
import dk.dma.epd.common.prototype.sensor.nmea.NmeaFileSensor;
import dk.dma.epd.common.prototype.sensor.nmea.NmeaSensor;
import dk.dma.epd.common.prototype.sensor.nmea.NmeaSerialSensor;
import dk.dma.epd.common.prototype.sensor.nmea.NmeaSerialSensorFactory;
import dk.dma.epd.common.prototype.sensor.nmea.NmeaTcpSensor;
import dk.dma.epd.common.prototype.sensor.nmea.NmeaUdpSensor;
import dk.dma.epd.common.prototype.sensor.pnt.PntHandler;
import dk.dma.epd.common.prototype.sensor.pnt.PntTime;
import dk.dma.epd.common.prototype.sensor.rpnt.MultiSourcePntHandler;
import dk.dma.epd.common.prototype.service.ChatServiceHandlerCommon;
import dk.dma.epd.common.prototype.service.MaritimeCloudService;
import dk.dma.epd.common.prototype.service.MsiNmServiceHandlerCommon;
import dk.dma.epd.common.prototype.settings.SensorSettings;
import dk.dma.epd.common.prototype.settings.SensorSettings.PntSourceSetting;
import dk.dma.epd.common.prototype.shoreservice.ShoreServicesCommon;
import dk.dma.epd.common.util.VersionInfo;
import dk.dma.epd.ship.ais.AisHandler;
import dk.dma.epd.ship.event.DragMouseMode;
import dk.dma.epd.ship.event.NavigationMouseMode;
import dk.dma.epd.ship.fal.FALManager;
import dk.dma.epd.ship.gui.MainFrame;
import dk.dma.epd.ship.gui.notification.NotificationCenter;
import dk.dma.epd.ship.gui.route.RouteManagerDialog;
import dk.dma.epd.ship.monalisa.MonaLisaRouteOptimization;
import dk.dma.epd.ship.nogo.NogoHandler;
import dk.dma.epd.ship.ownship.IOwnShipListener;
import dk.dma.epd.ship.ownship.OwnShipHandler;
import dk.dma.epd.ship.predictor.DynamicPredictor;
import dk.dma.epd.ship.predictor.DynamicPredictorHandler;
import dk.dma.epd.ship.predictor.DynamicPredictorSentenceParser;
import dk.dma.epd.ship.risk.RiskHandler;
import dk.dma.epd.ship.route.RouteManager;
import dk.dma.epd.ship.service.FALHandler;
import dk.dma.epd.ship.service.IntendedRouteHandler;
import dk.dma.epd.ship.service.RouteSuggestionHandler;
import dk.dma.epd.ship.service.StrategicRouteHandler;
import dk.dma.epd.ship.service.VoctHandler;
import dk.dma.epd.ship.service.shore.ShoreServices;
import dk.dma.epd.ship.service.voct.VOCTManager;
import dk.dma.epd.ship.settings.EPDSensorSettings;
import dk.dma.epd.ship.settings.EPDSettings;

/**
 * Main class with main method.
 * 
 * Starts up components, bean context and GUI.
 * 
 */
public final class EPDShip extends EPD implements IOwnShipListener {

    private static Logger LOG;

    MainFrame mainFrame;
    private MapHandler mapHandler;
    private NmeaSensor aisSensor;
    private NmeaSensor gpsSensor;
    private NmeaSensor msPntSensor;
    private NmeaSensor dynamicPredictorSensor;
    private PntHandler pntHandler;
    private MultiSourcePntHandler msPntHandler;
    private OwnShipHandler ownShipHandler;
    private DynamicPredictorHandler dynamicPredictorHandler;
    private RiskHandler riskHandler;
    private ShoreServicesCommon shoreServices;
    private MonaLisaRouteOptimization monaLisaRouteExchange;
    private NogoHandler nogoHandler;
    private TransponderFrame transponderFrame;
    private VoyageEventDispatcher voyageEventDispatcher;
    private VOCTManager voctManager;
    private DynamicPredictor dynamicPredictor;
    private DynamicPredictorSentenceParser dynamicPredictorParser;

    // Maritime Cloud services
    private IntendedRouteHandler intendedRouteHandler;
    private String optionalTitle = "";

    // private VoctHandler voctHandler;

    private PluginLoader pluginLoader;

    /**
     * Starts the program by initializing the various threads and spawning the main GUI
     * 
     * @param args
     */

    public static void main(String[] args) throws IOException {
        // Check if the home path has been specified via the command line
        String homePath = (args.length > 0) ? args[0] : null;
        new EPDShip(homePath);
    }

    /**
     * Constructor
     * 
     * @param path
     *            the home path to use
     */
    private EPDShip(String path) throws IOException {
        super();

        if (!StringUtils.isEmpty(path)) {
            homePath = Paths.get(path);

            try {
                optionalTitle = " - " + path.split("epd-ship")[1];
            } catch (Exception e) {

            }
        } else {
            homePath = determineHomePath(Paths.get(System.getProperty("user.home"), ".epd-ship"));
        }

        new Bootstrap().run(this,
                new String[] { "epd-ship.properties", "enc_navicon.properties", "settings.properties",
                        "transponder.xml" },
                new String[] { "routes", "layout/static", "shape/GSHHS_shp", "identities" });

        // Set up log4j logging
        LOG = LoggerFactory.getLogger(EPDShip.class);

        // Set default exception handler
        Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());

        // Determine version
        LOG.info("Starting eNavigation Prototype Display Ship - version " + VersionInfo.getVersionAndBuild());
        LOG.info("Copyright (C) 2011 Danish Maritime Authority");
        LOG.info("This program comes with ABSOLUTELY NO WARRANTY.");
        LOG.info("This is free software, and you are welcome to redistribute it under certain conditions.");
        LOG.info("For details see LICENSE file.");

        // Create the bean context (map handler)
        mapHandler = new MapHandler();

        // Load settings or get defaults and add to bean context
        settings = new EPDSettings();
        LOG.info("Using settings file: " + getSettings().getSettingsFile());
        settings.loadFromFile();
        mapHandler.add(settings);

        // Determine if instance already running and if that is allowed
        OneInstanceGuard guard = new OneInstanceGuard(getHomePath().resolve("epd.lock").toString());
        if (guard.isAlreadyRunning()) {
            handleEpdAlreadyRunning();
        }

        // start riskHandler
        startRiskHandler();

        // Enable PNT timer by adding it to bean context
        PntTime.init(settings.getSensorSettings().isUseTimeFromPnt());
        mapHandler.add(PntTime.getInstance());

        // Start position handler and add to bean context
        pntHandler = new PntHandler();
        mapHandler.add(pntHandler);

        // Start the multi-source PNT handler and add to bean context
        msPntHandler = new MultiSourcePntHandler();
        msPntHandler.addPntListener(pntHandler);
        mapHandler.add(msPntHandler);

        // Start AIS target monitoring
        aisHandler = new AisHandler(settings.getSensorSettings(), settings.getAisSettings());
        aisHandler.loadView();
        EPD.startThread(aisHandler, "AisHandler");
        mapHandler.add(aisHandler);

        // Start own-ship handler
        ownShipHandler = new OwnShipHandler(settings.getAisSettings());
        ownShipHandler.loadView();
        ownShipHandler.addListener(this);
        mapHandler.add(ownShipHandler);

        // Start dynamic predictor handler
        dynamicPredictorHandler = new DynamicPredictorHandler();
        mapHandler.add(dynamicPredictorHandler);

        // Start dynamic predictor sensor sentence parser
        dynamicPredictorParser = new DynamicPredictorSentenceParser();
        mapHandler.add(dynamicPredictorParser);

        // Maybe start dynamic prediction generator
        if (settings.getSensorSettings().isStartPredictionGenerator()) {
            dynamicPredictor = new DynamicPredictor();
            mapHandler.add(dynamicPredictor);
        }

        // Load routeManager and register as GPS data listener
        routeManager = RouteManager.loadRouteManager();
        mapHandler.add(routeManager);

        falManager = FALManager.loadFALManager();
        mapHandler.add(falManager);

        voctManager = VOCTManager.loadVOCTManager();
        mapHandler.add(voctManager);

        // Create shore services
        shoreServices = new ShoreServices(getSettings().getEnavSettings());
        mapHandler.add(shoreServices);

        // Create mona lisa route exchange
        monaLisaRouteExchange = new MonaLisaRouteOptimization();
        mapHandler.add(monaLisaRouteExchange);

        // Create NoGo handler
        nogoHandler = new NogoHandler();
        mapHandler.add(nogoHandler);

        // Create Maritime Cloud service
        maritimeCloudService = new MaritimeCloudService();
        mapHandler.add(maritimeCloudService);
        maritimeCloudService.start();

        strategicRouteHandler = new StrategicRouteHandler();
        mapHandler.add(strategicRouteHandler);

        // Create intended route handler
        intendedRouteHandler = new IntendedRouteHandler();
        intendedRouteHandler.updateSettings(settings.getEnavSettings());
        mapHandler.add(intendedRouteHandler);

        // Create the route suggestion handler
        // routeSuggestionHandler = new RouteSuggestionHandler();
        routeSuggestionHandler = RouteSuggestionHandler.loadRouteSuggestionHandler();
        mapHandler.add(routeSuggestionHandler);

        // Create a new MSI-NM handler
        msiNmHandler = new MsiNmServiceHandlerCommon();
        mapHandler.add(msiNmHandler);

        // Create a chat service handler
        chatServiceHandler = new ChatServiceHandlerCommon();
        mapHandler.add(chatServiceHandler);

        // Create voyage event dispatcher
        voyageEventDispatcher = new VoyageEventDispatcher();

        // Create identity handler
        identityHandler = new IdentityHandler();
        mapHandler.add(identityHandler);

        // Create VOCT handler
        voctHandler = new VoctHandler();
        mapHandler.add(voctHandler);

        // Create FAL Handler
        falHandler = new FALHandler();
        mapHandler.add(falHandler);

        // Start sensors
        startSensors();

        // Create plugin components

        try {
            pluginLoader = new PluginLoader(getProperties(), getHomePath(), getPropertyFileName());
            pluginLoader.createPluginComponents(new Consumer<Object>() {
                public void accept(Object comp) {
                    mapHandler.add(comp);
                }
            });
        } catch (Exception e) {
            LOG.error("Failed to load plugin container " + e.getMessage());
        }

        // pluginLoader.createPluginComponents(comp -> mapHandler.add(comp));

        final CountDownLatch guiCreated = new CountDownLatch(1);

        // Create and show GUI
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGUI();
                guiCreated.countDown();
            }
        });

        // Wait for gui to be created
        try {
            guiCreated.await();
        } catch (InterruptedException e) {
            LOG.error("Interrupted while waiting for GUI to be created", e);
        }

        // Create embedded transponder frame
        transponderFrame = new TransponderFrame(getHomePath().resolve("transponder.xml").toString(), true,
                mainFrame);
        mapHandler.add(transponderFrame);

        if (settings.getSensorSettings().isStartTransponder()) {
            transponderFrame.startTransponder();
        }

    }

    /**
     * Returns the current {@code EPDShore} instance
     * 
     * @return the current {@code EPDShore} instance
     */
    public static EPDShip getInstance() {
        return (EPDShip) instance;
    }

    /**
     * Returns the settings associated with the EPD system
     * 
     * @return the settings associated with the EPD system
     */
    @Override
    public EPDSettings getSettings() {
        return (EPDSettings) settings;
    }

    /**
     * Returns the default shore mouse mode service list
     * 
     * @return the default shore mouse mode service list
     */
    @Override
    public String[] getDefaultMouseModeServiceList() {
        String[] ret = new String[2];
        ret[0] = NavigationMouseMode.MODE_ID; // "Gestures"
        ret[1] = DragMouseMode.MODE_ID;
        return ret;
    }

    /**
     * Starts the sensors defined in the {@linkplain SensorSettings} and hook up listeners
     */
    @Override
    protected void startSensors() {
        EPDSensorSettings sensorSettings = getSettings().getSensorSettings();
        switch (sensorSettings.getAisConnectionType()) {
        case NONE:
            aisSensor = null;
            break;
        case TCP:
            aisSensor = new NmeaTcpSensor(sensorSettings.getAisHostOrSerialPort(),
                    sensorSettings.getAisTcpOrUdpPort());
            break;
        case UDP:
            aisSensor = new NmeaUdpSensor(sensorSettings.getAisTcpOrUdpPort());
            break;
        case SERIAL:
            // aisSensor = new NmeaSerialSensor(sensorSettings.getAisHostOrSerialPort());
            aisSensor = NmeaSerialSensorFactory.create(sensorSettings.getAisHostOrSerialPort(),
                    sensorSettings.getAisSerialPortBaudRate());
            break;
        case FILE:
            aisSensor = new NmeaFileSensor(sensorSettings.getAisFilename(), sensorSettings);
            break;
        default:
            LOG.error("Unknown sensor connection type: " + sensorSettings.getAisConnectionType());
        }

        switch (sensorSettings.getGpsConnectionType()) {
        case NONE:
            gpsSensor = null;
            break;
        case TCP:
            gpsSensor = new NmeaTcpSensor(sensorSettings.getGpsHostOrSerialPort(),
                    sensorSettings.getGpsTcpOrUdpPort());
            break;
        case UDP:
            gpsSensor = new NmeaUdpSensor(sensorSettings.getGpsTcpOrUdpPort());
            break;
        case SERIAL:
            gpsSensor = NmeaSerialSensorFactory.create(sensorSettings.getGpsHostOrSerialPort(),
                    sensorSettings.getGpsSerialPortBaudRate());
            break;
        case FILE:
            gpsSensor = new NmeaFileSensor(sensorSettings.getGpsFilename(), sensorSettings);
            break;
        default:
            LOG.error("Unknown sensor connection type: " + sensorSettings.getGpsConnectionType());
        }

        switch (sensorSettings.getMsPntConnectionType()) {
        case NONE:
            msPntSensor = null;
            break;
        case TCP:
            msPntSensor = new NmeaTcpSensor(sensorSettings.getMsPntHostOrSerialPort(),
                    sensorSettings.getMsPntTcpOrUdpPort());
            break;
        case UDP:
            msPntSensor = new NmeaUdpSensor(sensorSettings.getMsPntTcpOrUdpPort());
            break;
        case SERIAL:
            msPntSensor = NmeaSerialSensorFactory.create(sensorSettings.getMsPntHostOrSerialPort(),
                    sensorSettings.getMsPntSerialPortBaudRate());
            break;
        case FILE:
            msPntSensor = new NmeaFileSensor(sensorSettings.getMsPntFilename(), sensorSettings);
            break;
        default:
            LOG.error("Unknown sensor connection type: " + sensorSettings.getMsPntConnectionType());
        }
        // Only init sensor if dynamic prediction has been enabled.
        if (getSettings().getNavSettings().isDynamicPrediction()) {
            switch (sensorSettings.getDynamicPredictorConnectionType()) {
            case TCP:
                dynamicPredictorSensor = new NmeaTcpSensor(sensorSettings.getDynamicPredictorHostOrSerialPort(),
                        sensorSettings.getDynamicPredictorTcpOrUdpPort());
                break;
            case UDP:
                dynamicPredictorSensor = new NmeaUdpSensor(sensorSettings.getDynamicPredictorTcpOrUdpPort());
                break;
            case SERIAL:
                dynamicPredictorSensor = new NmeaSerialSensor(sensorSettings.getDynamicPredictorHostOrSerialPort(),
                        sensorSettings.getDynamicPredictorSerialPortBaudRate());
            default:
                dynamicPredictorSensor = null;
                break;
            }
        } else {
            dynamicPredictorSensor = null;
        }
        if (aisSensor != null) {
            aisSensor.addAisListener(aisHandler);
            aisSensor.addAisListener(ownShipHandler);
            aisSensor.start();
            mapHandler.add(aisSensor);
        }
        if (gpsSensor != null) {
            gpsSensor.start();
            mapHandler.add(gpsSensor);
        }
        if (msPntSensor != null) {
            msPntSensor.start();
            mapHandler.add(msPntSensor);
        }
        if (dynamicPredictorSensor != null) {
            dynamicPredictorSensor.addDynamicPredictorListener(dynamicPredictorParser);
            dynamicPredictorSensor.start();
            mapHandler.add(dynamicPredictorSensor);
        }

        // Hook pnt handler to sensor
        PntSourceSetting pntSource = sensorSettings.getPntSource();
        if (pntSource == PntSourceSetting.AUTO) {
            if (msPntSensor != null) {
                pntSource = PntSourceSetting.MSPNT;
            } else if (gpsSensor != null) {
                pntSource = PntSourceSetting.GPS;
            } else if (aisSensor != null) {
                pntSource = PntSourceSetting.AIS;
            }
        }

        if (pntSource == PntSourceSetting.AIS && aisSensor != null) {
            aisSensor.addPntListener(pntHandler);
            aisSensor.addPntListener(PntTime.getInstance());
        } else if (pntSource == PntSourceSetting.GPS && gpsSensor != null) {
            gpsSensor.addPntListener(pntHandler);
            gpsSensor.addPntListener(PntTime.getInstance());
        } else if (pntSource == PntSourceSetting.MSPNT && msPntHandler != null) {
            msPntSensor.addMsPntListener(msPntHandler);
            msPntSensor.addPntListener(msPntHandler);
            msPntSensor.addPntListener(PntTime.getInstance());
        }

    }

    /**
     * Stops all sensors and remove listeners
     */
    @Override
    protected void stopSensors() {
        // Stop AIS sensor
        if (aisSensor != null) {
            mapHandler.remove(aisSensor);
            aisSensor.removeAisListener(aisHandler);
            aisSensor.removeAisListener(ownShipHandler);
            aisSensor.removePntListener(pntHandler);
            aisSensor.removePntListener(PntTime.getInstance());
            stopSensor(aisSensor, 3000L);
            aisSensor = null;
        }

        // Stop GPS sensor
        if (gpsSensor != null) {
            mapHandler.remove(gpsSensor);
            gpsSensor.removePntListener(pntHandler);
            gpsSensor.removePntListener(PntTime.getInstance());
            stopSensor(gpsSensor, 3000L);
            gpsSensor = null;
        }

        // Stop multi-source PNT sensor
        if (msPntSensor != null) {
            mapHandler.remove(msPntSensor);
            msPntSensor.removeMsPntListener(msPntHandler);
            msPntSensor.removePntListener(msPntHandler);
            msPntSensor.removePntListener(PntTime.getInstance());
            stopSensor(msPntSensor, 3000L);
            msPntSensor = null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void settingsChanged(Type type) {
        if (type == Type.SENSOR) {
            LOG.warn("Restarting all sensors");
            stopSensors();
            startSensors();

        } else if (type == Type.CLOUD) {
            LOG.warn("Restarting Maritime Cloud connection");
            maritimeCloudService.stop();
            maritimeCloudService.start();

            // Update intended route filter settings.
            LOG.warn("Updating intended route filter settings.");
            this.intendedRouteHandler.updateSettings(this.settings.getEnavSettings());
        }
    }

    public void startRiskHandler() {
        riskHandler = new RiskHandler();
    }

    @Override
    protected String getPropertyFileName() {
        return "epd-ship.properties";
    }

    @Override
    protected void propertyLoadError(String msg, IOException e) {
        LOG.error(msg + e.getMessage());
    }

    void createAndShowGUI() {
        // Set the look and feel.
        initLookAndFeel();

        // Make sure we have nice window decorations.
        JFrame.setDefaultLookAndFeelDecorated(false);

        // Create and set up the main window
        mainFrame = new MainFrame(optionalTitle);
        mainFrame.setVisible(true);

        // Create the system tray
        systemTray = new SystemTrayCommon();
        mapHandler.add(systemTray);

        // Create the notification center
        notificationCenter = new NotificationCenter(getMainFrame());
        mapHandler.add(notificationCenter);

        // Create keybinding shortcuts
        makeKeyBindings();

    }

    private void makeKeyBindings() {
        JPanel content = (JPanel) mainFrame.getContentPane();
        InputMap inputMap = content.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);

        @SuppressWarnings("serial")
        Action zoomIn = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                mainFrame.getChartPanel().doZoom(0.5f);
            }
        };

        @SuppressWarnings("serial")
        Action zoomOut = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                mainFrame.getChartPanel().doZoom(2f);
            }
        };

        @SuppressWarnings("serial")
        Action centreOnShip = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                mainFrame.saveCentreOnShip();
            }
        };

        @SuppressWarnings("serial")
        Action newRoute = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                // newRouteBtn.requestFocusInWindow();
                mainFrame.getTopPanel().activateNewRouteButton();
            }
        };

        @SuppressWarnings("serial")
        Action routes = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                RouteManagerDialog routeManagerDialog = new RouteManagerDialog(mainFrame);
                routeManagerDialog.setVisible(true);
            }
        };

        @SuppressWarnings("serial")
        Action ais = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                mainFrame.getTopPanel().getAisDialog().setVisible(true);
            }
        };

        @SuppressWarnings("serial")
        Action panUp = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                mainFrame.getChartPanel().pan(1);
            }
        };
        @SuppressWarnings("serial")
        Action panDown = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                mainFrame.getChartPanel().pan(2);
            }
        };

        @SuppressWarnings("serial")
        Action panLeft = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                mainFrame.getChartPanel().pan(3);
            }
        };
        @SuppressWarnings("serial")
        Action panRight = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                mainFrame.getChartPanel().pan(4);
            }
        };

        inputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_ADD, 0), "ZoomIn");
        inputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_SUBTRACT, 0), "ZoomOut");
        inputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_C, 0), "centre");
        inputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_UP, 0), "panUp");
        inputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_DOWN, 0), "panDown");
        inputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_LEFT, 0), "panLeft");
        inputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_RIGHT, 0), "panRight");
        inputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_KP_UP, 0), "panUp");
        inputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_KP_DOWN, 0), "panDown");
        inputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_KP_LEFT, 0), "panLeft");
        inputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_KP_RIGHT, 0), "panRight");
        inputMap.put(KeyStroke.getKeyStroke("control N"), "newRoute");
        inputMap.put(KeyStroke.getKeyStroke("control R"), "routes");
        inputMap.put(KeyStroke.getKeyStroke("control M"), "msi-nm");
        inputMap.put(KeyStroke.getKeyStroke("control A"), "ais");

        content.getActionMap().put("ZoomOut", zoomOut);
        content.getActionMap().put("ZoomIn", zoomIn);
        content.getActionMap().put("centre", centreOnShip);
        content.getActionMap().put("newRoute", newRoute);
        content.getActionMap().put("routes", routes);
        content.getActionMap().put("ais", ais);
        content.getActionMap().put("panUp", panUp);
        content.getActionMap().put("panDown", panDown);
        content.getActionMap().put("panLeft", panLeft);
        content.getActionMap().put("panRight", panRight);

    }

    private void initLookAndFeel() {
        try {
            // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

            Properties props = new Properties();
            props.put("logoString", "EPD-Ship");
            props.put("backgroundPattern", "false");
            props.put("textAntiAliasingMode", "TEXT_ANTIALIAS_VBGR");
            // props.put("menuOpaque", "true");
            // props.put("tooltipCastShadow", "true");

            // small font
            // props.setProperty("controlTextFont", "Dialog 12");
            // props.setProperty("systemTextFont", "Dialog 12");
            // props.setProperty("userTextFont", "Dialog 12");
            // props.setProperty("menuTextFont", "Dialog 12");
            // props.setProperty("windowTitleFont", "Dialog bold 12");
            // props.setProperty("subTextFont", "Dialog 10");
            props.setProperty("controlTextFont", "Dialog 10");
            props.setProperty("systemTextFont", "Dialog 10");
            props.setProperty("userTextFont", "Dialog 10");
            props.setProperty("menuTextFont", "Dialog 10");
            props.setProperty("windowTitleFont", "Dialog bold 10");
            props.setProperty("subTextFont", "Dialog 8");

            // props.put("tooltipBorderSize", "15");
            // props.put("tooltipShadowSize", "15");

            // NoireLookAndFeel laf = new NoireLookAndFeel();
            HiFiLookAndFeel laf = new HiFiLookAndFeel();
            // NoireLookAndFeel.setCurrentTheme(props);
            HiFiLookAndFeel.setCurrentTheme(props);

            UIManager.setLookAndFeel(laf);

        } catch (Exception e) {
            LOG.error("Failed to set look and feed: " + e.getMessage());
        }

    }

    public void closeApp(boolean restart) {
        // Shutdown routine

        mainFrame.saveSettings();
        settings.saveToFile();
        routeManager.saveToFile();
        msiNmHandler.saveToFile();
        aisHandler.saveView();
        ownShipHandler.saveView();
        transponderFrame.shutdown();
        falManager.saveToFile();
        routeSuggestionHandler.saveToFile();

        // Stop the Maritime Cloud connection
        strategicRouteHandler.shutdown();
        routeSuggestionHandler.shutdown();
        intendedRouteHandler.shutdown();
        chatServiceHandler.shutdown();
        msiNmHandler.shutdown();
        maritimeCloudService.stop();

        // Stop the system tray
        systemTray.shutdown();

        // Stop sensors
        stopSensors();

        pluginLoader.closePlugins();

        LOG.info("Closing EPD-ship");
        this.restart = restart;
        System.exit(restart ? 2 : 0);

    }

    /**
     * Returns the MMSI of the own-ship, or null if not defined
     * 
     * @return the MMSI of the own-ship
     */
    @Override
    public Long getMmsi() {
        return ownShipHandler != null ? ownShipHandler.getMmsi() : null;
    }

    /**
     * Returns the maritime id of the own-ship, or null if not defined
     * 
     * @return the maritime id of the own-ship
     */
    @Override
    public MaritimeId getMaritimeId() {
        Long mmsi = getMmsi();
        return mmsi != null ? new MmsiId(mmsi.intValue()) : null;
    }

    /**
     * Returns the current position of the ship
     * 
     * @return the current position of the ship
     */
    @Override
    public Position getPosition() {
        return getPntHandler().getCurrentData().getPosition();
    }

    public NmeaSensor getAisSensor() {
        return aisSensor;
    }

    public NmeaSensor getGpsSensor() {
        return gpsSensor;
    }

    public PntHandler getPntHandler() {
        return pntHandler;
    }

    public MainFrame getMainFrame() {
        return mainFrame;
    }

    @Override
    public FALManager getFalManager() {
        return (FALManager) falManager;
    }

    /**
     * Returns a reference to the AIS handler
     * 
     * @return a reference to the AIS handler
     */
    @Override
    public AisHandler getAisHandler() {
        return (AisHandler) aisHandler;
    }

    public OwnShipHandler getOwnShipHandler() {
        return ownShipHandler;
    }

    @Override
    public RouteManager getRouteManager() {
        return (RouteManager) routeManager;
    }

    public MapHandler getMapHandler() {
        return mapHandler;
    }

    public ShoreServicesCommon getShoreServices() {
        return shoreServices;
    }

    public MonaLisaRouteOptimization getMonaLisaRouteExchange() {
        return monaLisaRouteExchange;
    }

    public double elapsed(long start) {
        double elapsed = System.nanoTime() - start;
        return elapsed / 1000000.0;
    }

    public RiskHandler getRiskHandler() {
        return riskHandler;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public StrategicRouteHandler getStrategicRouteHandler() {
        return (StrategicRouteHandler) strategicRouteHandler;
    }

    /**
     * {@inheritDoc}
     */
    public RouteSuggestionHandler getRouteSuggestionHandler() {
        return (RouteSuggestionHandler) routeSuggestionHandler;
    }

    /**
     * @return the falHandler
     */
    public FALHandler getFalHandler() {
        return (FALHandler) falHandler;
    }

    /**
     * Get the system wide voyage event dispatcher.
     * 
     * @return the voyageEventDispatcher
     */
    public VoyageEventDispatcher getVoyageEventDispatcher() {
        return voyageEventDispatcher;
    }

    /**
     * 
     * @return the voctManager
     */
    public VOCTManager getVoctManager() {
        return voctManager;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public NotificationCenter getNotificationCenter() {
        return (NotificationCenter) super.getNotificationCenter();
    }

    @Override
    public Path getHomePath() {
        return homePath;
    }

    /**
     * Returns a {@code Resource} instance which loads resource from the same class-loader/jar-file as the {@code EPDShip} class.
     * 
     * @return a new {@code Resource} instance
     */
    public static Resources res() {
        return Resources.get(EPDShip.class);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void ownShipUpdated(OwnShipHandler ownShipHandler) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void ownShipChanged(VesselTarget oldValue, VesselTarget newValue) {

        // Broadcast a message to remove the intended route
        if (getRouteManager().isRouteActive()) {
            intendedRouteHandler.broadcastIntendedRoute(null, false);
        }

        // Restart maritime cloud
        LOG.warn("Restarting Maritime Cloud connection");
        maritimeCloudService.stop();
        maritimeCloudService.start();

        // Broadcast a message to add the intended route
        if (getRouteManager().isRouteActive()) {
            intendedRouteHandler.broadcastIntendedRoute(getRouteManager().getActiveRoute(), true);
        }
    }
}