<commit_before>/*
 *    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.core.ContextOperations;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.mwc.cmap.core.CorePlugin;
import org.mwc.cmap.core.operations.CMAPOperation;
import org.mwc.cmap.core.property_support.RightClickSupport.RightClickContextItemGenerator;

import Debrief.Wrappers.FixWrapper;
import Debrief.Wrappers.TrackWrapper;
import Debrief.Wrappers.Track.TrackSegment;
import MWC.GUI.Editable;
import MWC.GUI.Layer;
import MWC.GUI.Layers;
import MWC.GenericData.HiResDate;
import MWC.GenericData.WorldDistance;
import MWC.GenericData.WorldLocation;
import MWC.GenericData.WorldSpeed;
import MWC.GenericData.WorldVector;

/**
 * @author ian.mayo
 */
public class RemoveTrackJumps implements RightClickContextItemGenerator
{

	/**
	 * @param parent
	 * @param theLayers
	 * @param parentLayers
	 * @param subjects
	 */
	public void generate(final IMenuManager parent, final Layers theLayers,
			final Layer[] parentLayers, final Editable[] subjects)
	{
		// we're only going to work with one item
		Collection<Editable> points = null;
		String tmpTitle = null;
		if (subjects.length == 1)
		{
			// is it a track?
			final Editable thisE = subjects[0];
			if (thisE instanceof TrackSegment)
			{
				TrackSegment ts = (TrackSegment) thisE;
				points = ts.getData();
				tmpTitle = "Remove jumps for selected track";
			}
		}
		else
		{
			// see if it's a series of points
			final Editable thisE = subjects[0];
			if (thisE instanceof FixWrapper)
			{
				// collate the points into a collection
				points = new Vector<Editable>();

				// loop through the data
				for (int i = 0; i < subjects.length; i++)
				{
					Editable editable = subjects[i];
					points.add(editable);
				}

				tmpTitle = "Remove jumps in selected positions";
			}
		}

		// ok, is it worth going for?
		if (points != null)
		{
			final String title = tmpTitle;
			final Collection<Editable> permPoints = points;

			// right,stick in a separator
			parent.add(new Separator());

			final TrackWrapper track = (TrackWrapper) parentLayers[0];

			// and the new drop-down list of interpolation frequencies

			// yes, create the action
			final Action convertToTrack = new Action(title)
			{
				public void run()
				{
					// ok, go for it.
					// sort it out as an operation
					final IUndoableOperation removeJumps = new RemoveJumps(title,
							theLayers, track, permPoints);

					// ok, stick it on the buffer
					CorePlugin.run(removeJumps);
				}

			};
			parent.add(convertToTrack);
		}

	}

	static class RemoveJumps extends CMAPOperation
	{

		/**
		 * the parent to update on completion
		 */
		private final Layers _layers;

		/**
		 * list of new fixes we're creating
		 */
		private Collection<Editable> _points;

		/**
		 * the track we're interpolating
		 */
		private final TrackWrapper _track;

		private HashMap<FixWrapper, WorldLocation> _newFixes;

		public RemoveJumps(String title, Layers theLayers, TrackWrapper track,
				Collection<Editable> points)
		{
			super(title);
			_layers = theLayers;
			_track = track;
			_points = points;
		}

		/**
		 * store the limits of a leg
		 * 
		 * @author ian
		 * 
		 */
		static class Leg
		{
			/**
			 * the start of the leg
			 * 
			 */
			final long startTime;

			/**
			 * the end of the leg
			 * 
			 */
			final long endTime;

			/**
			 * the offset from where the last point should be, to where it is measured
			 * as
			 * 
			 */
			final WorldVector offset;

			/**
			 * define a leg, used for removing jumps
			 * 
			 * @param startP
			 *          the start-point (presumed to be a GPS fix)
			 * @param prevP
			 *          the point immediately before the jump point
			 * @param jumpP
			 *          the point before the jump
			 * @param lockP
			 *          the point after the jump (presumed to be a GPS fix)
			 */
			public Leg(FixWrapper startP, FixWrapper prevP, FixWrapper jumpP,
					FixWrapper lockP)
			{
				startTime = startP.getTime().getDate().getTime();
				endTime = lockP.getTime().getDate().getTime();

				// ok, calculate the offset
				WorldVector lastStep = jumpP.getLocation()
						.subtract(prevP.getLocation());
				offset = lockP.getLocation()
						.subtract(jumpP.getLocation().add(lastStep));
			}

			/**
			 * is this time in our time period. Note, we deliberately exclude the
			 * first & last times, since those positions don't have an offset applied
			 * 
			 * @param theTime
			 *          the time we're testing against
			 * @return yes/no
			 */
			public boolean contains(long theTime)
			{
				return ((theTime > startTime) && (theTime < endTime));
			}

			/**
			 * calculate the offset to apply to the supplied position
			 * 
			 * @param time
			 * @return
			 */
			public WorldVector offsetFor(final long time)
			{
				WorldVector res = null;

				// just check this isn't the start or end time
				if ((time != startTime) && (time != endTime))
				{
					// ok, how far along time period are we
					double tDelta = time - startTime;
					double proportion = tDelta / (endTime - startTime);
					double newDistance = offset.getRange() * proportion;

					// generate the offset
					res = new WorldVector(offset.getBearing(), newDistance, 0);
				}

				return res;
			}
		}

		public IStatus execute(final IProgressMonitor monitor, final IAdaptable info)
				throws ExecutionException
		{
			// get ready to store the old positions
			_newFixes = new HashMap<FixWrapper, WorldLocation>();

			// PART ONE - FIND THE JUMPS
			ArrayList<Leg> legs = getLegs(_points);

			// did we find any?
			applyOffsets(legs, _points, _newFixes);

			// sorted, do the update
			if (_layers != null)<commit_msg>javadoc, plus message when no jumps found<commit_after>/*
 *    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.core.ContextOperations;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.mwc.cmap.core.CorePlugin;
import org.mwc.cmap.core.operations.CMAPOperation;
import org.mwc.cmap.core.property_support.RightClickSupport.RightClickContextItemGenerator;

import Debrief.Wrappers.FixWrapper;
import Debrief.Wrappers.TrackWrapper;
import Debrief.Wrappers.Track.TrackSegment;
import MWC.GUI.Editable;
import MWC.GUI.Layer;
import MWC.GUI.Layers;
import MWC.GenericData.HiResDate;
import MWC.GenericData.WorldDistance;
import MWC.GenericData.WorldLocation;
import MWC.GenericData.WorldSpeed;
import MWC.GenericData.WorldVector;

/**
 * @author ian.mayo
 */
public class RemoveTrackJumps implements RightClickContextItemGenerator
{

	/**
	 * @param parent
	 *          menu
	 * @param theLayers
	 *          the whole layers
	 * @param parentLayers
	 *          the parent layers for the selected items
	 * @param subjects
	 *          the selected items
	 */
	public void generate(final IMenuManager parent, final Layers theLayers,
			final Layer[] parentLayers, final Editable[] subjects)
	{
		Collection<Editable> points = null;
		String tmpTitle = null;

		// how many items are selected?
		if (subjects.length == 1)
		{
			// is it a track?
			final Editable thisE = subjects[0];
			if (thisE instanceof TrackSegment)
			{
				TrackSegment ts = (TrackSegment) thisE;
				points = ts.getData();
				tmpTitle = "Remove jumps for selected track";
			}
		}
		else
		{
			// see if it's a series of points
			final Editable thisE = subjects[0];
			if (thisE instanceof FixWrapper)
			{
				// collate the points into a collection
				points = new Vector<Editable>();

				// loop through the data
				for (int i = 0; i < subjects.length; i++)
				{
					Editable editable = subjects[i];
					points.add(editable);
				}

				tmpTitle = "Remove jumps in selected positions";
			}
		}

		// ok, is it worth going for?
		if (points != null)
		{
			final String title = tmpTitle;
			final Collection<Editable> permPoints = points;

			// right,stick in a separator
			parent.add(new Separator());

			final TrackWrapper track = (TrackWrapper) parentLayers[0];

			// and the new drop-down list of interpolation frequencies

			// yes, create the action
			final Action convertToTrack = new Action(title)
			{
				public void run()
				{
					// ok, go for it.
					// sort it out as an operation
					final IUndoableOperation removeJumps = new RemoveJumps(title,
							theLayers, track, permPoints);

					// ok, stick it on the buffer
					CorePlugin.run(removeJumps);
				}

			};
			parent.add(convertToTrack);
		}

	}

	static class RemoveJumps extends CMAPOperation
	{

		/**
		 * the parent to update on completion
		 */
		private final Layers _layers;

		/**
		 * list of new fixes we're creating
		 */
		private Collection<Editable> _points;

		/**
		 * the track we're interpolating
		 */
		private final TrackWrapper _track;

		private HashMap<FixWrapper, WorldLocation> _newFixes;

		public RemoveJumps(String title, Layers theLayers, TrackWrapper track,
				Collection<Editable> points)
		{
			super(title);
			_layers = theLayers;
			_track = track;
			_points = points;
		}

		/**
		 * store the limits of a leg
		 * 
		 * @author ian
		 * 
		 */
		static class Leg
		{
			/**
			 * the start of the leg
			 * 
			 */
			final long startTime;

			/**
			 * the end of the leg
			 * 
			 */
			final long endTime;

			/**
			 * the offset from where the last point should be, to where it is measured
			 * as
			 * 
			 */
			final WorldVector offset;

			/**
			 * define a leg, used for removing jumps
			 * 
			 * @param startP
			 *          the start-point (presumed to be a GPS fix)
			 * @param prevP
			 *          the point immediately before the jump point
			 * @param jumpP
			 *          the point before the jump
			 * @param lockP
			 *          the point after the jump (presumed to be a GPS fix)
			 */
			public Leg(FixWrapper startP, FixWrapper prevP, FixWrapper jumpP,
					FixWrapper lockP)
			{
				startTime = startP.getTime().getDate().getTime();
				endTime = lockP.getTime().getDate().getTime();

				// ok, calculate the offset
				WorldVector lastStep = jumpP.getLocation()
						.subtract(prevP.getLocation());
				offset = lockP.getLocation()
						.subtract(jumpP.getLocation().add(lastStep));
			}

			/**
			 * is this time in our time period. Note, we deliberately exclude the
			 * first & last times, since those positions don't have an offset applied
			 * 
			 * @param theTime
			 *          the time we're testing against
			 * @return yes/no
			 */
			public boolean contains(long theTime)
			{
				return ((theTime > startTime) && (theTime < endTime));
			}

			/**
			 * calculate the offset to apply to the supplied position
			 * 
			 * @param time
			 * @return
			 */
			public WorldVector offsetFor(final long time)
			{
				WorldVector res = null;

				// just check this isn't the start or end time
				if ((time != startTime) && (time != endTime))
				{
					// ok, how far along time period are we
					double tDelta = time - startTime;
					double proportion = tDelta / (endTime - startTime);
					double newDistance = offset.getRange() * proportion;

					// generate the offset
					res = new WorldVector(offset.getBearing(), newDistance, 0);
				}

				return res;
			}
		}

		public IStatus execute(final IProgressMonitor monitor, final IAdaptable info)
				throws ExecutionException
		{
			// get ready to store the old positions
			_newFixes = new HashMap<FixWrapper, WorldLocation>();

			// PART ONE - FIND THE JUMPS
			ArrayList<Leg> legs = getLegs(_points);

			// did we find any?
			if (legs.size() == 0)
			{
				CorePlugin.showMessage("Remove jumps",
						"No jumps were detected in the track segment\n\n" +
						"A jump is detected when a step has a calculated speed\n" +
						"three times larger than the previous step.");
			}

			// ok, apply the offsets
			applyOffsets(legs, _points, _newFixes);

			// sorted, do the update
			if (_layers != null)
{'fraction_non_alphanumeric': 0.09752221339305064, 'fraction_numerical': 0.002672831033735462, 'mean_word_length': 2.8359656414519256, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7154705', 'n_tokens_mistral': 5042, 'n_tokens_neox': 4049, 'n_words': 1307}