/***************************************************************************** * Limpet - the Lightweight InforMation ProcEssing Toolkit * http://limpet.info * * (C) 2015-2016, Deep Blue C Technologies 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 info.limpet.data.operations.spatial; import info.limpet.ICollection; import info.limpet.ICommand; import info.limpet.IContext; import info.limpet.IOperation; import info.limpet.IQuantityCollection; import info.limpet.IStore; import info.limpet.IStoreItem; import info.limpet.data.commands.AbstractCommand; import info.limpet.data.impl.samples.StockTypes; import info.limpet.data.impl.samples.StockTypes.Temporal; import info.limpet.data.impl.samples.StockTypes.Temporal.AngleDegrees; import info.limpet.data.impl.samples.TemporalLocation; import info.limpet.data.operations.CollectionComplianceTests; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.measure.Measure; public class GenerateCourseAndSpeedOperation implements IOperation<IStoreItem> { protected abstract static class DistanceOperation extends AbstractCommand<IStoreItem> { public DistanceOperation(List<IStoreItem> selection, IStore store, String title, String description, IContext context) { super(title, description, store, false, false, selection, context); } @Override public void execute() { // get the unit List<IStoreItem> outputs = new ArrayList<IStoreItem>(); String prefix = getOutputName(); if (prefix == null) { return; } // ok, generate the new series for (int i = 0; i < getInputs().size(); i++) { IQuantityCollection<?> target = getOutputCollection(prefix + getInputs().get(i).getName()); outputs.add(target); // store the output super.addOutput(target); } // start adding values. performCalc(outputs); // tell each series that we're a dependent Iterator<IStoreItem> iter = getInputs().iterator(); while (iter.hasNext()) { ICollection iCollection = (ICollection) iter.next(); iCollection.addDependent(this); } // ok, done getStore().addAll(outputs); } protected abstract IQuantityCollection<?> getOutputCollection( String trackList); @Override protected void recalculate(IStoreItem subject) { // clear out the lists, first Iterator<IStoreItem> iter = getOutputs().iterator(); while (iter.hasNext()) { IQuantityCollection<?> qC = (IQuantityCollection<?>) iter.next(); qC.getValues().clear(); } // update the results performCalc(getOutputs()); } /** * wrap the actual operation. We're doing this since we need to separate it from the core * "execute" operation in order to support dynamic updates * * @param unit * @param outputs */ private void performCalc(List<IStoreItem> outputs) { // get a calculator to use final IGeoCalculator calc = GeoSupport.getCalculator(); // remember what series have been updated List<ICollection> updated = new ArrayList<ICollection>(); // do some clearing first Iterator<IStoreItem> iter = getInputs().iterator(); Iterator<IStoreItem> oIter = outputs.iterator(); while (iter.hasNext()) { TemporalLocation thisTrack = (TemporalLocation) iter.next(); IStoreItem thisOut = oIter.next(); // ok, walk through it Iterator<Point2D> pITer = thisTrack.getLocations().iterator(); Iterator<Long> tIter = thisTrack.getTimes().iterator(); // remember the last value long lastTime = 0; Point2D lastLocation = null; while (pITer.hasNext()) { Point2D geometry = pITer.next(); long thisTime = tIter.next(); if (lastLocation != null) { calcAndStore(thisOut, calc, lastTime, lastLocation, thisTime, geometry); } // and remember the values lastLocation = geometry; lastTime = thisTime; } updated.add((ICollection) thisOut); } Iterator<ICollection> iter2 = updated.iterator(); while (iter2.hasNext()) { ICollection iC = (ICollection) iter2.next(); iC.fireDataChanged(); } } protected abstract void calcAndStore(IStoreItem thisOut, final IGeoCalculator calc, final long timeA, final Point2D locA, final long timeB, final Point2D locB); } private final CollectionComplianceTests aTests = new CollectionComplianceTests(); protected boolean appliesTo(List<IStoreItem> selection) { boolean nonEmpty = aTests.nonEmpty(selection); boolean allTemporal = aTests.allTemporal(selection); return nonEmpty && allTemporal && aTests.allNonQuantity(selection) && aTests .allLocation(selection); } public Collection<ICommand<IStoreItem>> actionsFor( List<IStoreItem> selection, IStore destination, IContext context) { Collection<ICommand<IStoreItem>> res = new ArrayList<ICommand<IStoreItem>>(); if (appliesTo(selection)) { int len = selection.size(); final String title; if (len > 1) { title = "Generate course for track"; } else { title = "Generate course for tracks"; } ICommand<IStoreItem> genCourse = new DistanceOperation(selection, destination, "Generate calculated course", title, context) { protected IQuantityCollection<?> getOutputCollection(String title) { return new StockTypes.Temporal.AngleDegrees(title, this); } @Override protected String getOutputName() { return getContext().getInput("Generate course", "Please provide a dataset prefix", "Generated course for "); } protected void calcAndStore(IStoreItem output, final IGeoCalculator calc, long lastTime, final Point2D locA, long thisTime, final Point2D locB) { // get the output dataset Temporal.AngleDegrees target = (AngleDegrees) output; // now find the bearing between them double angleDegs = calc.getAngleBetween(locA, locB); if (angleDegs < 0) { angleDegs += 360; } target.add(thisTime, Measure.valueOf(angleDegs, target.getUnits())); } }; ICommand<IStoreItem> genSpeed = new DistanceOperation(selection, destination, "Generate calculated speed", title, context) { protected IQuantityCollection<?> getOutputCollection(String title) { return new StockTypes.Temporal.SpeedMSec(title, this); } @Override protected String getOutputName() { return getContext().getInput("Generate speed", "Please provide a dataset prefix", "Generated speed for "); } protected void calcAndStore(IStoreItem output, final IGeoCalculator calc, long lastTime, final Point2D locA, long thisTime, final Point2D locB) { // get the output dataset Temporal.SpeedMSec target = (Temporal.SpeedMSec) output; // now find the range between them double thisDist = calc.getDistanceBetween(locA, locB); double calcTime = thisTime - lastTime; double thisSpeed = thisDist / (calcTime / 1000d); target.add(thisTime, Measure.valueOf(thisSpeed, target.getUnits())); } }; res.add(genCourse); res.add(genSpeed); } return res; } }