org.mwc.debrief.sensorfusion.views.DataSupport.java Source code

Java tutorial

Introduction

Here is the source code for org.mwc.debrief.sensorfusion.views.DataSupport.java

Source

/*
 *    Debrief - the Open Source Maritime Analysis Application
 *    http://debrief.info
 *
 *    (C) 2000-2014, PlanetMayo Ltd
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the Eclipse Public License v1.0
 *    (http://www.eclipse.org/legal/epl-v10.html)
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 */
package org.mwc.debrief.sensorfusion.views;

import java.awt.Color;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.FixedMillisecond;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.RectangleInsets;

import Debrief.Wrappers.FixWrapper;
import Debrief.Wrappers.SensorContactWrapper;
import Debrief.Wrappers.SensorWrapper;
import Debrief.Wrappers.TrackWrapper;
import MWC.GUI.Editable;
import MWC.GUI.JFreeChart.ColouredDataItem;
import MWC.GenericData.HiResDate;
import MWC.GenericData.Watchable;
import MWC.GenericData.WatchableList;
import MWC.GenericData.WorldDistance;
import MWC.GenericData.WorldLocation;
import MWC.GenericData.WorldVector;
import MWC.TacticalData.Fix;

public class DataSupport {

    private static final int THRESHOLD_FOR_IRRELEVANT_DATA = 45;
    private static double _previousVal;

    /**
     * Creates a chart.
     * 
     * @param dataset
     *          a dataset.
     * 
     * @return A chart.
     */
    public static JFreeChart createChart(final XYDataset dataset) {

        final JFreeChart chart = ChartFactory.createTimeSeriesChart("Bearing Management", // title
                "Time", // x-axis label
                "Bearing", // y-axis label
                dataset, // data
                false, // create legend?
                true, // generate tooltips?
                false // generate URLs?
        );

        chart.setBackgroundPaint(Color.white);

        final XYPlot plot = (XYPlot) chart.getPlot();
        plot.setBackgroundPaint(Color.lightGray);
        plot.setAxisOffset(new RectangleInsets(0, 0, 0, 0));
        plot.setDomainGridlinePaint(Color.white);
        plot.setRangeGridlinePaint(Color.white);
        plot.setDomainCrosshairVisible(false);
        plot.setRangeCrosshairVisible(false);

        plot.setOrientation(PlotOrientation.HORIZONTAL);

        final XYItemRenderer r = plot.getRenderer();
        if (r instanceof XYLineAndShapeRenderer) {
            final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;
            renderer.setBaseShapesVisible(true);
            renderer.setBaseShapesFilled(true);
        }

        final DateAxis axis = (DateAxis) plot.getDomainAxis();
        axis.setDateFormatOverride(new SimpleDateFormat("HH:mm.ss"));

        return chart;

    }

    abstract public static class TacticalSeries extends TimeSeries {
        public TacticalSeries(final String name) {
            super(name);
        }

        abstract public boolean getVisible();

        abstract public Color getColor();

        /**
         * 
         */
        private static final long serialVersionUID = 1L;

    }

    public static class TrackSeries extends TacticalSeries {

        private final WatchableList _myTrack;

        public TrackSeries(final String name, final WatchableList subject) {
            super(name);
            _myTrack = subject;
        }

        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        @Override
        public boolean getVisible() {
            if (_myTrack != null)
                return _myTrack.getVisible();
            else
                return true;
        }

        @Override
        public Color getColor() {
            return _myTrack.getColor();
        }

    }

    public static class SensorSeries extends TacticalSeries {
        final protected SensorWrapper _subject;

        public SensorSeries(final String name, final SensorWrapper subject) {
            super(name);
            _subject = subject;
        }

        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        @Override
        public boolean getVisible() {
            if (_subject != null)
                return _subject.getVisible();
            else
                return true;
        }

        @Override
        public Color getColor() {
            return _subject.getColor();
        }

        public SensorWrapper getSensor() {
            return _subject;
        }

    }

    public static long stepInterval() {
        final long[] intervals = { 1000, 5000, 60000, 300000 };
        final int index = (int) (Math.random() * 4);
        return intervals[index];
    }

    public static long delay() {
        final long delay = (int) (Math.random() * 210) * 60000 + (int) (Math.random() * 5 * 1000);
        return delay;
    }

    public static long duration() {
        final long delay = (int) (Math.random() * 90) * 60000 + (int) (Math.random() * 500 * 1000);
        return delay;
    }

    //
    // /**
    // * Creates a dataset, consisting of two series of monthly data.
    // *
    // * @return The dataset.
    // */
    // public static TimeSeriesCollection createDataset()
    // {
    // TimeSeriesCollection dataset = new TimeSeriesCollection();
    //
    // final long _start = (long) (Math.random() * 1000000);
    // final long _end = (long) (_start + 12000000 + Math.random() * 100000);
    //
    // int ctr = 0;
    //
    // int MAX_TRACKS = 1;
    // for (int i = 0; i < MAX_TRACKS; i++)
    // {
    // final long _step = 60000;
    // final long _thisStart = _start;
    // final long _thisEnd = _end;
    // long _this = _thisStart;
    //
    // TimeSeries s1 = new TrackSeries("track:" + i, null);
    // double theVal = Math.random() * 360;
    // while (_this < _thisEnd)
    // {
    // theVal = theVal - 1 + (Math.random() * 2);
    // s1.add(new FixedMillisecond(_this), theVal);
    // ctr++;
    // _this += _step;
    // }
    // dataset.addSeries(s1);
    // }
    //
    // int MAX_SENSORS = 5;
    // for (int i = 0; i < MAX_SENSORS; i++)
    // {
    // final long _step = stepInterval();
    // final long _thisStart = _start + delay();
    // long _thisEnd = _thisStart + duration();
    // _thisEnd = Math.min(_thisEnd, _end);
    // long _this = _thisStart;
    //
    // TimeSeries s1 = new SensorSeries("sensor:" + i, null);
    // double theVal = Math.random() * 360;
    // while (_this < _thisEnd)
    // {
    // theVal = theVal - 1 + (Math.random() * 2);
    // s1.add(new FixedMillisecond(_this), theVal);
    // ctr++;
    // _this += _step;
    // }
    // dataset.addSeries(s1);
    // }
    //
    // System.out.println(ctr + " points created");
    //
    // return dataset;
    // }

    public static void tracksFor(final TrackWrapper primary, final WatchableList[] secondaries,
            final TimeSeriesCollection newData) {
        if (secondaries != null) {
            for (int i = 0; i < secondaries.length; i++) {
                final WatchableList thisS = secondaries[i];
                final TrackSeries thisT = new TrackSeries(thisS.getName(), thisS);
                final Enumeration<Editable> priPts = primary.getPositions();
                _previousVal = Double.NaN;
                while (priPts.hasMoreElements()) {
                    final FixWrapper thisP = (FixWrapper) priPts.nextElement();
                    final Watchable[] nearest = thisS.getNearestTo(thisP.getDTG());
                    if (nearest != null)
                        if (nearest.length > 0) {
                            final Watchable thisLoc = nearest[0];
                            final WorldVector offset = thisLoc.getLocation().subtract(thisP.getLocation());
                            final double thisVal = MWC.Algorithms.Conversions.Rads2Degs(offset.getBearing());

                            thisT.add(create(thisP.getTime(), thisVal, thisP.getColor()));
                        }
                }
                if (!thisT.isEmpty())
                    newData.addSeries(thisT);
            }
        }

    }

    public static void sensorDataFor(final TrackWrapper primary, final TimeSeriesCollection newData,
            final HashMap<SensorWrapper, SensorSeries> index) {

        index.clear();

        final Enumeration<Editable> sensors = primary.getSensors().elements();
        while (sensors.hasMoreElements()) {
            final SensorWrapper sensor = (SensorWrapper) sensors.nextElement();
            final SensorSeries series = new SensorSeries(sensor.getName(), sensor);
            final Enumeration<Editable> cuts = sensor.elements();
            _previousVal = Double.NaN;

            index.put(sensor, series);
            while (cuts.hasMoreElements()) {
                final SensorContactWrapper scw = (SensorContactWrapper) cuts.nextElement();
                final double thisVal = scw.getBearing();

                // wrap this in a try/catch - in case there are multiple entries
                try {
                    series.add(create(scw.getTime(), thisVal, scw.getColor()));
                } catch (final Exception e) {
                    // no probs, just ignore the new value
                }
            }

            if (!series.isEmpty())
                newData.addSeries(series);
        }
    }

    /**
     * Deletes any sensor data that is outside the start/finish period of the
     * primary track.
     * 
     * @param primary
     * @param index
     * @return
     */
    public static ArrayList<SensorWrapper> trimToTrackPeriod(final TrackWrapper primary) {
        ArrayList<SensorWrapper> toDelete = new ArrayList<SensorWrapper>();
        final HiResDate startPeriod = primary.getStartDTG();
        final HiResDate finishPeriod = primary.getEndDTG();
        Enumeration<Editable> numer = primary.getSensors().elements();
        while (numer.hasMoreElements()) {
            final SensorWrapper thisS = (SensorWrapper) numer.nextElement();
            final HiResDate startDTG = thisS.getStartDTG();
            final HiResDate endDTG = thisS.getEndDTG();
            if (startPeriod.greaterThan(endDTG)) {
                // this sensor is before the track even starts
                toDelete.add(thisS);
            }
            if (finishPeriod.lessThan(startDTG)) {
                // this sensor is after the track ends
                toDelete.add(thisS);
            }
        }

        return toDelete;
    }

    /**
     * Deletes all blocks of sensor data that are more than 45 degrees from an
     * secondary track.
     * 
     * @return
     */
    public static ArrayList<SensorWrapper> trimToSensorNearSubjectTracks(final TrackWrapper primary,
            final WatchableList[] secondaries) {
        ArrayList<SensorWrapper> toRemove = new ArrayList<SensorWrapper>();
        ArrayList<SensorWrapper> toKeep = new ArrayList<SensorWrapper>();

        if (primary == null || secondaries == null)
            return toRemove;

        Enumeration<Editable> sensors = primary.getSensors().elements();
        while (sensors.hasMoreElements()) {
            SensorWrapper sensor = (SensorWrapper) sensors.nextElement();

            // ok, remember this one
            toRemove.add(sensor);

            final Enumeration<Editable> contacts = sensor.elements();
            // loop though the individual sensor contact objects
            while (sensor != null && (contacts.hasMoreElements())) {
                final SensorContactWrapper contact = (SensorContactWrapper) contacts.nextElement();
                // check this sensor contact has a bearing
                if (!contact.getHasBearing()) {
                    // hey, no bearing - we aren't sufficiently able to decide if
                    // it can be ditched.
                    toKeep.add(sensor);
                    sensor = null;
                } else {
                    final HiResDate contactTime = contact.getDTG();
                    // loop through each secondary track
                    for (int i = 0; i < secondaries.length; i++) {
                        final WatchableList thisS = secondaries[i];

                        final Watchable[] secondaryFixes = thisS.getNearestTo(contactTime);
                        final Watchable[] primaryFixes = primary.getNearestTo(contactTime);
                        double bearing = 0;
                        if (secondaryFixes != null && secondaryFixes.length > 0) {
                            if (primaryFixes != null && primaryFixes.length > 0) {
                                WorldLocation wl1 = secondaryFixes[0].getLocation();
                                WorldLocation wl2 = primaryFixes[0].getLocation();
                                bearing = wl1.bearingFrom(wl2);
                            }
                        }

                        double bearingDelta = contact.getBearing() - Math.toDegrees(bearing);
                        if (Math.abs(bearingDelta) < THRESHOLD_FOR_IRRELEVANT_DATA) {
                            // ok, this sensor is relevant
                            toKeep.add(sensor);

                            // now clear the sensor, as a marker to move on to the next
                            // sensor
                            sensor = null;
                        }
                    }

                }
            } // end loop through sensor contacts
        }

        // ok, we've built up a list of sensors to keep. We now need to get rid of
        // the other ones
        Iterator<SensorWrapper> keepers = toKeep.iterator();
        while (keepers.hasNext()) {
            SensorWrapper keepMe = (SensorWrapper) keepers.next();
            toRemove.remove(keepMe);
        }

        return toRemove;
    }

    private static ColouredDataItem create(final HiResDate hiResDate, final double thisVal, final Color color) {
        double val = thisVal;
        if (val < 0)
            val += 360;

        // aaah, but is it a jump?
        boolean connectToPrevious = true;
        if (!Double.isNaN(_previousVal)) {
            final double delta = Math.abs(val - _previousVal);
            if (delta > 100) {
                connectToPrevious = false;
            }
        }
        _previousVal = val;
        final ColouredDataItem cd = new ColouredDataItem(new FixedMillisecond(hiResDate.getDate().getTime()), val,
                color, connectToPrevious, null);

        return cd;
    }

    static public final class DataSupportTest extends junit.framework.TestCase {
        private HashMap<SensorWrapper, SensorSeries> _trackIndex;
        private TimeSeriesCollection _timeData;
        private TrackWrapper _primary;
        private SensorWrapper _sw1;
        private SensorWrapper _sw2a;
        private SensorWrapper _sw2b;

        public void setUp() {
            _trackIndex = new HashMap<SensorWrapper, SensorSeries>();
            _timeData = new TimeSeriesCollection();
            _primary = getDummyPrimary();
        }

        private TrackWrapper getDummyPrimary() {
            final TrackWrapper tw = new TrackWrapper();

            final WorldLocation loc_1 = new WorldLocation(0, 0, 0);
            final FixWrapper fw1 = new FixWrapper(new Fix(new HiResDate(100, 10000),
                    loc_1.add(new WorldVector(33, new WorldDistance(100, WorldDistance.METRES), null)), 10, 110));
            fw1.setLabel("fw1");
            final FixWrapper fw2 = new FixWrapper(new Fix(new HiResDate(200, 20000),
                    loc_1.add(new WorldVector(33, new WorldDistance(200, WorldDistance.METRES), null)), 20, 120));
            fw2.setLabel("fw2");
            final FixWrapper fw3 = new FixWrapper(new Fix(new HiResDate(300, 30000),
                    loc_1.add(new WorldVector(33, new WorldDistance(300, WorldDistance.METRES), null)), 30, 130));
            fw3.setLabel("fw3");
            final FixWrapper fw4 = new FixWrapper(new Fix(new HiResDate(400, 40000),
                    loc_1.add(new WorldVector(33, new WorldDistance(400, WorldDistance.METRES), null)), 40, 140));
            fw4.setLabel("fw4");
            final FixWrapper fw5 = new FixWrapper(new Fix(new HiResDate(500, 50000),
                    loc_1.add(new WorldVector(33, new WorldDistance(500, WorldDistance.METRES), null)), 50, 150));
            fw5.setLabel("fw5");
            tw.addFix(fw1);
            tw.addFix(fw2);
            tw.addFix(fw3);
            tw.addFix(fw4);
            tw.addFix(fw5);
            // also give it some sensor data
            _sw1 = new SensorWrapper("title one");
            _sw1.setVisible(true);
            final SensorContactWrapper scwa1 = new SensorContactWrapper("aaa", new HiResDate(150, 0), null,
                    new Double(15), null, null, null, 0, null);
            final SensorContactWrapper scwa2 = new SensorContactWrapper("bbb", new HiResDate(180, 0), null,
                    new Double(15), null, null, null, 0, null);
            final SensorContactWrapper scwa3 = new SensorContactWrapper("ccc", new HiResDate(250, 0), null,
                    new Double(150), null, null, null, 0, null);
            _sw1.add(scwa1);
            _sw1.add(scwa2);
            _sw1.add(scwa3);
            tw.add(_sw1);
            _sw2a = new SensorWrapper("title two");
            _sw2a.setVisible(true);
            final SensorContactWrapper scw1 = new SensorContactWrapper("ddd", new HiResDate(260, 0), null, null,
                    null, null, null, 0, null);
            final SensorContactWrapper scw2 = new SensorContactWrapper("eee", new HiResDate(280, 0), null, null,
                    null, null, null, 0, null);
            // this time is greater than the primary track finish period
            final SensorContactWrapper scw3 = new SensorContactWrapper("fff", new HiResDate(650, 0), null, null,
                    null, null, null, 0, null);
            _sw2a.add(scw1);
            _sw2a.add(scw2);
            _sw2a.add(scw3);
            _sw2b = new SensorWrapper("title three");
            _sw2b.setVisible(true);
            final SensorContactWrapper scw1b = new SensorContactWrapper("ddd", new HiResDate(760, 0), null, null,
                    null, null, null, 0, null);
            final SensorContactWrapper scw2b = new SensorContactWrapper("eee", new HiResDate(880, 0), null, null,
                    null, null, null, 0, null);
            // this time is greater than the primary track finish period
            final SensorContactWrapper scw3b = new SensorContactWrapper("fff", new HiResDate(950, 0), null, null,
                    null, null, null, 0, null);
            _sw2b.add(scw1b);
            _sw2b.add(scw2b);
            _sw2b.add(scw3b);
            tw.add(_sw1);
            tw.add(_sw2a);
            tw.add(_sw2b);

            return tw;
        }

        private TrackWrapper getDummySecondary() {
            final TrackWrapper tw = new TrackWrapper();

            final WorldLocation loc_1 = new WorldLocation(0, 0, 0);
            final FixWrapper fw1 = new FixWrapper(new Fix(new HiResDate(100, 10000),
                    loc_1.add(new WorldVector(33, new WorldDistance(100, WorldDistance.METRES), null)), 10, 110));
            fw1.setLabel("fw1");
            final FixWrapper fw2 = new FixWrapper(new Fix(new HiResDate(200, 20000),
                    loc_1.add(new WorldVector(33, new WorldDistance(200, WorldDistance.METRES), null)), 20, 120));
            fw2.setLabel("fw2");
            final FixWrapper fw3 = new FixWrapper(new Fix(new HiResDate(300, 30000),
                    loc_1.add(new WorldVector(33 + 200, new WorldDistance(300, WorldDistance.METRES), null)), 30,
                    130));
            fw3.setLabel("fw3");

            tw.addFix(fw1);
            tw.addFix(fw2);
            tw.addFix(fw3);

            return tw;
        }

        private TrackWrapper[] getDummyTracks() {
            final TrackWrapper[] tracks = new TrackWrapper[1];
            final TrackWrapper track = getDummySecondary();
            tracks[0] = track;
            return tracks;
        }

        public void testTrimToTrackPeriod() {
            final TrackWrapper[] secondaries = new TrackWrapper[0];

            DataSupport.tracksFor(_primary, secondaries, _timeData);

            DataSupport.sensorDataFor(_primary, _timeData, _trackIndex);

            assertTrue(_sw1.getVisible());
            assertTrue(_sw2a.getVisible());
            assertEquals(3, _primary.getSensors().size());
            ArrayList<SensorWrapper> res = DataSupport.trimToTrackPeriod(_primary);
            assertNotNull(res);
            assertEquals(1, res.size());
        }

        public void testTrimToSensorNearSubjectTracks() {
            final TrackWrapper[] secondaries = getDummyTracks();

            DataSupport.tracksFor(_primary, secondaries, _timeData);

            DataSupport.sensorDataFor(_primary, _timeData, _trackIndex);

            assertTrue(_sw1.getVisible());
            assertTrue(_sw2a.getVisible());
            ArrayList<SensorWrapper> res = DataSupport.trimToSensorNearSubjectTracks(_primary, secondaries);
            assertEquals("contains sensors", 2, res.size());
        }
    }

}