/*****************************************************************************
* 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.IBaseTemporalCollection;
import info.limpet.ICollection;
import info.limpet.IContext;
import info.limpet.IObjectCollection;
import info.limpet.IOperation;
import info.limpet.IQuantityCollection;
import info.limpet.IStore;
import info.limpet.IStoreGroup;
import info.limpet.IStoreItem;
import info.limpet.ITemporalQuantityCollection.InterpMethod;
import info.limpet.data.commands.AbstractCommand;
import info.limpet.data.impl.samples.StockTypes.ILocations;
import info.limpet.data.impl.samples.StockTypes.NonTemporal;
import info.limpet.data.impl.samples.StockTypes.NonTemporal.Location;
import info.limpet.data.impl.samples.TemporalLocation;
import info.limpet.data.operations.CollectionComplianceTests;
import info.limpet.data.operations.CollectionComplianceTests.TimePeriod;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
public abstract class TwoTrackOperation implements IOperation<IStoreItem>
{
public abstract static class DistanceOperation extends
AbstractCommand<IStoreItem>
{
private final IBaseTemporalCollection _timeProvider;
private final CollectionComplianceTests aTests = new CollectionComplianceTests();
public DistanceOperation(List<IStoreItem> selection, IStore store,
String title, String description, IBaseTemporalCollection timeProvider,
IContext context)
{
super(title, description, store, false, false, selection, context);
_timeProvider = timeProvider;
}
@Override
public void execute()
{
// get the unit
List<IStoreItem> outputs = new ArrayList<IStoreItem>();
// put the names into a string
String title = getOutputName();
// ok, generate the new series
IQuantityCollection<?> target = getOutputCollection(title,
_timeProvider != null);
outputs.add(target);
// store the output
super.addOutput(target);
// start adding values.
performCalc();
// 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
List<IStoreItem> res = new ArrayList<IStoreItem>();
res.add(target);
getStore().addAll(res);
}
protected abstract IQuantityCollection<?> getOutputCollection(
String trackList, boolean isTemporal);
@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();
}
/**
* 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
*/
@SuppressWarnings("unchecked")
private void performCalc()
{
ICollection track1 = (ICollection) getInputs().get(0);
ICollection track2 = (ICollection) getInputs().get(1);
// get a calculator to use
final IGeoCalculator calc = GeoSupport.getCalculator();
if (_timeProvider != null)
{
// and the bounding period
Collection<ICollection> selection = new ArrayList<ICollection>();
selection.add(track1);
selection.add(track2);
TimePeriod period = aTests.getBoundingTime(selection);
// check it's valid
if (period.invalid())
{
System.err.println("Insufficient coverage for datasets");
return;
}
// ok, let's start by finding our time sync
IBaseTemporalCollection times = aTests.getOptimalTimes(period,
selection);
// check we were able to find some times
if (times == null)
{
System.err.println("Unable to find time source dataset");
return;
}
// and now we can start looping through
Iterator<Long> tIter = times.getTimes().iterator();
while (tIter.hasNext())
{
long thisTime = (long) tIter.next();
if (thisTime >= period.getStartTime()
&& thisTime <= period.getEndTime())
{
Point2D locA = locationFor(track1, thisTime);
Point2D locB = locationFor(track2, thisTime);
if (locA != null && locB != null)
{
calcAndStore(calc, locA, locB, thisTime);
}
else
{
System.err.println("Not calculating location at time:" + thisTime
+ " - insufficient coverage");
}
}
}
}
else
{
// ok, we're doing an indexed version
// find one wiht more than one item
final IObjectCollection<Point2D> primary;
final IObjectCollection<Point2D> secondary;
if (track1.getValuesCount() > 1)
{
primary = (IObjectCollection<Point2D>) track1;
secondary = (IObjectCollection<Point2D>) track2;
}
else
{
primary = (IObjectCollection<Point2D>) track2;
secondary = (IObjectCollection<Point2D>) track1;
}
for (int j = 0; j < primary.getValuesCount(); j++)
{
final Point2D locA, locB;
locA = primary.getValues().get(j);
if (secondary.getValuesCount() > 1)
{
locB = secondary.getValues().get(j);
}
else
{
locB = secondary.getValues().get(0);
}
calcAndStore(calc, locA, locB, null);
}
}
// ok, share the updates
Iterator<IStoreItem> oIter = getOutputs().iterator();
while (oIter.hasNext())
{
IStoreItem iStoreItem = (IStoreItem) oIter.next();
iStoreItem.fireDataChanged();
}
}
private Point2D locationFor(ICollection track, final Long thisTime)
{
final Point2D locOne;
if (track instanceof IBaseTemporalCollection)
{
TemporalLocation tl = (TemporalLocation) track;
locOne = tl.interpolateValue(thisTime, InterpMethod.Linear);
}
else
{
NonTemporal.Location tl = (Location) track;
locOne = tl.getValues().iterator().next();
}
return locOne;
}
protected abstract void calcAndStore(final IGeoCalculator calc,
final Point2D locA, final Point2D locB, Long time);
}
private final CollectionComplianceTests aTests = new CollectionComplianceTests();
protected boolean appliesTo(List<IStoreItem> selection)
{
boolean nonEmpty = getATests().nonEmpty(selection);
boolean equalLength = getATests().allEqualLengthOrSingleton(selection);
boolean canInterpolate = getATests()
.suitableForTimeInterpolation(selection);
boolean onlyTwo = getATests().exactNumber(selection, 2);
boolean hasContents = getATests().allHaveData(selection);
boolean equalOrInterp = equalLength || canInterpolate;
boolean allLocation = getATests().allLocation(selection);
return nonEmpty && equalOrInterp && onlyTwo && allLocation && hasContents;
}
public CollectionComplianceTests getATests()
{
return aTests;
}
/**
* utility operation to extract the location datasets from the selection
* (walking down into groups as necessary)
*
* @param selection
* @return
*/
protected List<IStoreItem> getLocationDatasets(List<IStoreItem> selection)
{
List<IStoreItem> collatedTracks = new ArrayList<IStoreItem>();
// hmm, they may be composite tracks - extract the location data
Iterator<IStoreItem> sIter = selection.iterator();
while (sIter.hasNext())
{
IStoreItem iStoreItem = (IStoreItem) sIter.next();
if (iStoreItem instanceof IStoreGroup)
{
IStoreGroup group = (IStoreGroup) iStoreItem;
Iterator<IStoreItem> kids = group.iterator();
while (kids.hasNext())
{
IStoreItem thisItem = (IStoreItem) kids.next();
if (thisItem instanceof ILocations)
{
IStoreItem thisI = (IStoreItem) thisItem;
collatedTracks.add(thisI);
}
}
}
else if (iStoreItem instanceof ILocations)
{
collatedTracks.add(iStoreItem);
}
}
return collatedTracks;
}
}