/***************************************************************************** * 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; import static javax.measure.unit.SI.METRE; import static javax.measure.unit.SI.SECOND; import info.limpet.IBaseTemporalCollection; import info.limpet.ICollection; import info.limpet.IObjectCollection; import info.limpet.IQuantityCollection; import info.limpet.IStoreGroup; import info.limpet.IStoreItem; import info.limpet.ITemporalQuantityCollection.InterpMethod; import info.limpet.data.impl.TemporalQuantityCollection; import info.limpet.data.impl.samples.StockTypes; 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.store.StoreGroup; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.measure.Measurable; import javax.measure.converter.UnitConverter; import javax.measure.quantity.Quantity; import javax.measure.unit.Dimension; import javax.measure.unit.SI; import javax.measure.unit.Unit; public class CollectionComplianceTests { /** * check if the specific number of arguments are supplied * * @param selection * @param num * @return */ public boolean exactNumber(final List<? extends IStoreItem> selection, final int num) { return selection.size() == num; } /** * check if the series are all non locations * * @param selection * @return true/false */ public boolean allNonLocation(List<? extends IStoreItem> selection) { // are they all non location? boolean allValid = true; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; Class<?> theClass = thisC.storedClass(); if (Point2D.class.equals(theClass)) { allValid = false; break; } } else { allValid = false; break; } } return allValid; } /** * check if the series are all non locations * * @param selection * @return true/false */ public boolean allLocation(List<? extends IStoreItem> selection) { // are they all non location? boolean allValid = true; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ILocations) { } else { allValid = false; break; } } return allValid; } /** * check if the series are all quantity datasets * * @param selection * @return true/false */ public boolean allNonTemporal(List<? extends IStoreItem> selection) { // are they all temporal? boolean allValid = true; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; if (thisC.isTemporal()) { // oops, no allValid = false; break; } } else { allValid = false; break; } } return allValid; } /** * check if the series are all quantity datasets * * @param selection * @return true/false */ public boolean allNonQuantity(List<? extends IStoreItem> selection) { // are they all temporal? boolean allValid = true; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; if (thisC.isQuantity()) { // oops, no allValid = false; break; } } else { allValid = false; break; } } return allValid; } /** * determine if these datasets are suited to a temporal operation - where we * interpolate time values * * @param selection * @return */ public boolean suitableForTimeInterpolation( List<? extends IStoreItem> selection) { // are suitable boolean suitable = selection.size() >= 2; Long startT = null; Long endT = null; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; if (thisC.isQuantity() || thisC instanceof ILocations) { if (thisC.isTemporal()) { IBaseTemporalCollection tq = (IBaseTemporalCollection) thisC; long thisStart = tq.start(); long thisEnd = tq.finish(); if (startT == null) { startT = thisStart; endT = thisEnd; } else { // check the overlap if (thisStart > endT || thisEnd < startT) { suitable = false; break; } } } } else { // oops, no suitable = false; break; } } else { suitable = false; break; } } return suitable && startT != null; } /** * check if the series are all quantity datasets * * @param selection * @return true/false */ public boolean allQuantity(List<? extends IStoreItem> selection) { // are they all temporal? boolean allValid = true; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; if (!thisC.isQuantity()) { // oops, no allValid = false; break; } } else { allValid = false; break; } } return allValid; } /** * check if the series are all quantity datasets * * @param selection * @return true/false */ public boolean nonEmpty(List<? extends IStoreItem> selection) { return selection.size() > 0; } /** * check if the series are all have equal dimensions * * @param selection * @return true/false */ public boolean allEqualDimensions(List<? extends IStoreItem> selection) { // are they all temporal? boolean allValid = false; Dimension theD = null; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; if (thisC.isQuantity()) { IQuantityCollection<?> qc = (IQuantityCollection<?>) thisC; Dimension thisD = qc.getDimension(); if (theD == null) { theD = thisD; } else { if (thisD.equals(theD)) { // all fine. allValid = true; } else { allValid = false; break; } } } } else { // oops, no allValid = false; break; } } return allValid; } /** * check if the series all have equal units * * @param selection * @return true/false */ public boolean allEqualUnits(List<? extends IStoreItem> selection) { // are they all temporal? boolean allValid = true; Unit<?> theD = null; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; if (thisC.isQuantity()) { IQuantityCollection<?> qc = (IQuantityCollection<?>) thisC; Unit<?> thisD = qc.getUnits(); if (theD == null) { theD = thisD; } else { if (!thisD.equals(theD)) { allValid = false; break; } } } else { // oops, no allValid = false; break; } } else { allValid = false; break; } } return allValid; } /** * check if the series are all time series datasets (temporal) * * @param selection * @return true/false */ public boolean hasTemporal(List<? extends IStoreItem> selection) { // are they all temporal? boolean allValid = false; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; if (thisC.isTemporal()) { allValid = true; break; } } } return allValid; } /** * check if the series are all time series datasets (temporal) * * @param selection * @return true/false */ public boolean allTemporal(List<? extends IStoreItem> selection) { // are they all temporal? boolean allValid = true; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; if (!thisC.isTemporal()) { // oops, no allValid = false; break; } } else { // oops, no allValid = false; break; } } return allValid; } /** * check if the series are at least one temporal dataset, plus one or more * singletons * * @param selection * @return true/false */ public boolean allTemporalOrSingleton(List<? extends IStoreItem> selection) { // are they all temporal? boolean allValid = false; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; if (thisC.isTemporal()) { allValid = true; } else { // check if it's not a singleton if (thisC.getValuesCount() != 1) { // oops, no allValid = false; break; } } } else { // oops, no allValid = false; break; } } return allValid; } /** * check if the series are all of equal length, or singletons * * @param selection * @return true/false */ public boolean allEqualLengthOrSingleton(List<? extends IStoreItem> selection) { // are they all temporal? boolean allValid = true; int size = -1; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; int thisSize = thisC.getValuesCount(); // valid, check the size if (size == -1) { // ok, is this a singleton? if (thisSize != 1) { // nope, it's a real array store it. size = thisSize; } } else { if (thisSize != size && thisSize != 1) { // oops, no allValid = false; break; } } } else { allValid = false; break; } } return allValid; } /** * check if the series are all of equal length * * @param selection * @return true/false */ public boolean allEqualLength(List<? extends IStoreItem> selection) { // are they all temporal? boolean allValid = true; int size = -1; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; // valid, check the size if (size == -1) { size = thisC.getValuesCount(); } else { if (size != thisC.getValuesCount()) { // oops, no allValid = false; break; } } } else { allValid = false; break; } } return allValid; } public boolean allCollections(List<? extends IStoreItem> selection) { boolean res = true; Iterator<? extends IStoreItem> iter = selection.iterator(); while (iter.hasNext()) { IStoreItem storeItem = iter.next(); if (!(storeItem instanceof ICollection)) { res = false; break; } } return res; } public boolean minNumberOfGroups(List<IStoreItem> selection, final int count) { int res = 0; Iterator<? extends IStoreItem> iter = selection.iterator(); while (iter.hasNext()) { IStoreItem storeItem = iter.next(); if (storeItem instanceof StoreGroup) { res++; } } return res > count; } public int getNumberOfGroups(List<IStoreItem> selection) { int res = 0; Iterator<? extends IStoreItem> iter = selection.iterator(); while (iter.hasNext()) { IStoreItem storeItem = iter.next(); if (storeItem instanceof StoreGroup) { res++; } } return res; } public boolean allGroups(List<IStoreItem> selection) { boolean res = true; Iterator<? extends IStoreItem> iter = selection.iterator(); while (iter.hasNext()) { IStoreItem storeItem = iter.next(); if (!(storeItem instanceof StoreGroup)) { res = false; break; } } return res; } /** * convenience test to verify if children of the supplied item can all be * treated as tracks * * @param selection * one or more group objects * @return yes/no */ public int getNumberOfTracks(List<IStoreItem> selection) { int count = 0; Iterator<? extends IStoreItem> iter = selection.iterator(); while (iter.hasNext()) { IStoreItem storeItem = iter.next(); boolean valid = true; if (storeItem instanceof StoreGroup) { // ok, check the contents StoreGroup group = (StoreGroup) storeItem; valid = isATrack(group); // special case: we can miss out course & speed if there's just a single // stationery location, and there's a single location if (!valid && hasSingletonLocation(group)) { valid = true; } } else if (storeItem instanceof StockTypes.NonTemporal.Location) { valid = true; } else { valid = false; } if (valid) { count++; } } return count; } private boolean hasSingletonLocation(List<IStoreItem> kids) { boolean res = false; Iterator<IStoreItem> iter = kids.iterator(); while (iter.hasNext()) { IStoreItem item = iter.next(); if (item instanceof ILocations) { ILocations locs = (ILocations) item; if (locs.getLocations().size() == 1) { res = true; break; } } } return res; } /** * test for if a group contains enough data for us to treat it as a track * * @param group * the group of tracks * @return yes/no */ public boolean isATrack(IStoreGroup group) { boolean res = true; // ok, keep looping through, to check we have the right types if (!isPresent(group, METRE.divide(SECOND).getDimension())) { return false; } // ok, keep looping through, to check we have the right types if (!isPresent(group, SI.RADIAN.getDimension())) { return false; } if (!hasLocation(group)) { return false; } return res; } /** * convenience test to verify if children of the supplied item can all be * treated as tracks * * @param selection * one or more group objects * @return yes/no */ public boolean allChildrenAreTracks(List<IStoreItem> selection) { boolean res = true; Iterator<? extends IStoreItem> iter = selection.iterator(); while (iter.hasNext()) { IStoreItem storeItem = iter.next(); if (storeItem instanceof IStoreGroup) { // ok, check the contents StoreGroup group = (StoreGroup) storeItem; res = isATrack(group); } } return res; } /** * get any layers that we contain location data * * @param selection * one or more group objects * @return yes/no */ public ArrayList<StoreGroup> getChildTrackGroups(List<IStoreItem> selection) { ArrayList<StoreGroup> res = new ArrayList<StoreGroup>(); Iterator<? extends IStoreItem> iter = selection.iterator(); while (iter.hasNext()) { IStoreItem storeItem = iter.next(); if (storeItem instanceof StoreGroup) { // ok, check the contents StoreGroup group = (StoreGroup) storeItem; List<IStoreItem> kids = group; Iterator<IStoreItem> kIter = kids.iterator(); while (kIter.hasNext()) { IStoreItem storeItem2 = (IStoreItem) kIter.next(); if (storeItem2 instanceof ILocations) { res.add(group); } } } } return res; } /** * see if all the collections have the specified dimension * * @param items * to check * @param dimension * we're looking for * @return yes/no */ public boolean allHaveDimension(List<ICollection> kids, Dimension dim) { boolean res = true; Iterator<ICollection> iter = kids.iterator(); while (iter.hasNext()) { IStoreItem item = iter.next(); if (item instanceof IQuantityCollection<?>) { IQuantityCollection<?> coll = (IQuantityCollection<?>) item; if (!coll.getDimension().equals(dim)) { res = false; break; } } } return res; } /** * see if a collection of the specified dimension is present * * @param items * to check * @param dimension * we're looking for * @return yes/no */ private boolean isPresent(Collection<IStoreItem> kids, Dimension dim) { boolean res = false; Iterator<IStoreItem> iter = kids.iterator(); while (iter.hasNext()) { IStoreItem item = iter.next(); if (item instanceof IQuantityCollection<?>) { IQuantityCollection<?> coll = (IQuantityCollection<?>) item; if (coll.getDimension().equals(dim)) { res = true; break; } } } return res; } /** * see if a collection of the specified dimension is present * * @param items * to check * @param dimension * we're looking for * @return yes/no */ private boolean hasLocation(Collection<IStoreItem> kids) { boolean res = false; Iterator<IStoreItem> iter = kids.iterator(); while (iter.hasNext()) { IStoreItem item = iter.next(); if (item instanceof ILocations) { res = true; break; } } return res; } /** * find the item in the list with the specified dimension * * @param kids * items to examine * @param dimension * dimension we need to be present * @return yes/no */ public IQuantityCollection<?> collectionWith(Collection<IStoreItem> kids, Dimension dimension, final boolean walkTree) { IQuantityCollection<?> res = null; Iterator<IStoreItem> iter = kids.iterator(); while (iter.hasNext()) { IStoreItem item = iter.next(); if (item instanceof StoreGroup && walkTree) { StoreGroup group = (StoreGroup) item; res = collectionWith(group, dimension, walkTree); if (res != null) { break; } } else if (item instanceof IQuantityCollection<?>) { IQuantityCollection<?> coll = (IQuantityCollection<?>) item; if (coll.getDimension().equals(dimension)) { res = coll; break; } } } return res; } /** * check the list has a location collection * * @param kids * items to examine * @param dimension * dimension we need to be present * @return yes/no */ public IObjectCollection<?> someHaveLocation(List<IStoreItem> kids) { IObjectCollection<?> res = null; Iterator<IStoreItem> iter = kids.iterator(); while (iter.hasNext()) { IStoreItem item = iter.next(); if (item instanceof StoreGroup) { StoreGroup group = (StoreGroup) item; res = someHaveLocation(group); if (res != null) { break; } } else if (item instanceof IObjectCollection<?>) { IObjectCollection<?> coll = (IObjectCollection<?>) item; if (coll instanceof ILocations) { res = coll; break; } } } return res; } public int getLongestCollectionLength(List<IStoreItem> selection) { // find the longest time series. Iterator<IStoreItem> iter = selection.iterator(); int longest = -1; while (iter.hasNext()) { ICollection thisC = (ICollection) iter.next(); longest = Math.max(longest, thisC.getValuesCount()); } return longest; } public IBaseTemporalCollection getLongestTemporalCollections( List<IStoreItem> selection) { // find the longest time series. Iterator<IStoreItem> iter = selection.iterator(); IBaseTemporalCollection longest = null; while (iter.hasNext()) { ICollection thisC = (ICollection) iter.next(); if (thisC.isTemporal() && (thisC.isQuantity() || thisC instanceof ILocations)) { IBaseTemporalCollection tqc = (IBaseTemporalCollection) thisC; // check it has some data if (tqc.getTimes().size() > 0) { if (longest == null) { longest = tqc; } else { // store the longest one ICollection asColl = (ICollection) longest; longest = thisC.getValuesCount() > asColl.getValuesCount() ? tqc : longest; } } } } return longest; } public boolean allHaveData(List<IStoreItem> selection) { // are they all non location? boolean allValid = true; for (int i = 0; i < selection.size(); i++) { IStoreItem thisI = selection.get(i); if (thisI instanceof ICollection) { ICollection thisC = (ICollection) thisI; if (thisC.getValuesCount() == 0) { allValid = false; break; } } else { allValid = false; break; } } return allValid; } /** * find the time period for overlapping data * * @param items * @return */ public TimePeriod getBoundingTime(final Collection<ICollection> items) { TimePeriod res = null; Iterator<ICollection> iter = items.iterator(); while (iter.hasNext()) { ICollection iCollection = (ICollection) iter.next(); // allow for empty value. sometimes our logic allows null objects for some // data types if (iCollection != null && iCollection.isTemporal()) { IBaseTemporalCollection timeC = (IBaseTemporalCollection) iCollection; // check it has some data if (timeC.getTimes().size() > 0) { if (res == null) { res = new TimePeriod(timeC.start(), timeC.finish()); } else { res.setStartTime(Math.max(res.getStartTime(), timeC.start())); res.setEndTime(Math.min(res.getEndTime(), timeC.finish())); } } } } return res; } /** * find the best collection to use as a time-base. Which collection has the * most values within the specified time period? * * @param period * (optional) period in which we count valid times * @param items * list of datasets we're examining * @return most suited collection */ public IBaseTemporalCollection getOptimalTimes(TimePeriod period, Collection<ICollection> items) { IBaseTemporalCollection res = null; long resScore = 0; Iterator<ICollection> iter = items.iterator(); while (iter.hasNext()) { ICollection iCollection = (ICollection) iter.next(); // occasionally we may store a null dataset, since it is optional in some // circumstances if (iCollection != null && iCollection.isTemporal()) { IBaseTemporalCollection timeC = (IBaseTemporalCollection) iCollection; Iterator<Long> times = timeC.getTimes().iterator(); int score = 0; while (times.hasNext()) { long long1 = (long) times.next(); if (period == null || period.contains(long1)) { score++; } } if (res == null || score > resScore) { res = timeC; resScore = score; } } } return res; } /** * retrieve the value at the specified time (even if it's a non-temporal * collection) * * @param iCollection * set of locations to use * @param thisTime * time we're need a location for * @return */ @SuppressWarnings("unchecked") public double valueAt(ICollection iCollection, long thisTime, Unit<?> requiredUnits) { Measurable<Quantity> res; // just check it's not an empty set, since we return zero for empty dataset if (iCollection == null) { return 0; } else if (iCollection.isQuantity()) { IQuantityCollection<?> iQ = (IQuantityCollection<?>) iCollection; // just check it's not empty (which can happen during edits) if (iQ.getValuesCount() == 0) { return 0; } if (iCollection.isTemporal()) { TemporalQuantityCollection<?> tQ = (TemporalQuantityCollection<?>) iCollection; res = (Measurable<Quantity>) tQ.interpolateValue(thisTime, InterpMethod.Linear); } else { IQuantityCollection<?> qC = (IQuantityCollection<?>) iCollection; res = (Measurable<Quantity>) qC.getValues().iterator().next(); } if (res != null) { UnitConverter converter = iQ.getUnits().getConverterTo(requiredUnits); Unit<?> sourceUnits = iQ.getUnits(); double doubleValue = res.doubleValue((Unit<Quantity>) sourceUnits); double result = converter.convert(doubleValue); return result; } else { return 0; } } else { throw new RuntimeException("Tried to get value of non quantity data type"); } } /** * retrieve the location at the specified time (even if it's a non-temporal * collection) * * @param iCollection * set of locations to use * @param thisTime * time we're need a location for * @return */ public Point2D locationFor(ICollection iCollection, Long thisTime) { Point2D res = null; if (iCollection.isTemporal()) { TemporalLocation tLoc = (TemporalLocation) iCollection; res = tLoc.interpolateValue(thisTime, InterpMethod.Linear); } else { NonTemporal.Location tLoc = (Location) iCollection; if (tLoc.getValuesCount() > 0) { res = tLoc.getValues().iterator().next(); } } return res; } public static class TimePeriod { private long startTime; private long endTime; public TimePeriod(final long tStart, final long tEnd) { setStartTime(tStart); setEndTime(tEnd); } public boolean invalid() { return getEndTime() < getStartTime(); } public boolean contains(long time) { return getStartTime() <= time && getEndTime() >= time; } public long getStartTime() { return startTime; } public void setStartTime(long startTime) { this.startTime = startTime; } public long getEndTime() { return endTime; } public void setEndTime(long endTime) { this.endTime = endTime; } } }