/*****************************************************************************
* 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;
}
}