package au.gov.amsa.navigation; import static com.google.common.base.Optional.absent; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.davidmoten.rtree.RTree; import com.github.davidmoten.rtree.geometry.Geometries; import com.github.davidmoten.rtree.geometry.Point; import com.google.common.base.Optional; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import au.gov.amsa.navigation.VesselPosition.NavigationalStatus; import fj.Equal; import fj.F; import fj.Ord; import fj.Ordering; import fj.P3; import fj.data.Option; import fj.data.Set; class State { private static Logger log = LoggerFactory.getLogger(State.class); private static final int R_TREE_MAX_CHILDREN = 10; private final Map<Identifier, Set<VesselPosition>> map; private final RTree<VesselPosition, Point> tree; private final Optional<VesselPosition> last; private final long counter; private final Set<VesselPosition> byTimeAndId; private State(Map<Identifier, Set<VesselPosition>> map, Set<VesselPosition> byTimeAndId, RTree<VesselPosition, Point> tree, Optional<VesselPosition> last, long counter) { this.map = Collections.unmodifiableMap(map); this.byTimeAndId = byTimeAndId; this.tree = tree; this.last = last; this.counter = counter; } State() { this(new HashMap<Identifier, Set<VesselPosition>>(), EMPTY, RTree.star().maxChildren(R_TREE_MAX_CHILDREN).<VesselPosition, Point> create(), Optional.<VesselPosition> absent(), 0); } private static Ord<VesselPosition> ordering = toOrdering(Comparators.timeIdMessageIdComparator); private static Set<VesselPosition> EMPTY = Set.empty(ordering); Optional<VesselPosition> nextPosition() { if (last.isPresent()) return next(last.get()); else return Optional.absent(); } private Optional<VesselPosition> next(VesselPosition p) { Iterator<VesselPosition> it = map.get(p.id()).split(p)._3().iterator(); return it.hasNext() ? Optional.of(it.next()) : Optional.<VesselPosition> absent(); } RTree<VesselPosition, Point> tree() { return tree; } Optional<VesselPosition> last() { return last; } private static Set<VesselPosition> add(Set<VesselPosition> set, VesselPosition t) { return set.insert(t); } private static final VesselPosition.Builder BUILDER = VesselPosition.builder() .positionAisNmea(absent()).cls(VesselClass.A).cogDegrees(Optional.of(0.0)) .headingDegrees(absent()).data(absent()).lat(0.0).lon(0.0).lengthMetres(absent()) .navigationalStatus(NavigationalStatus.NOT_DEFINED).shipStaticAisNmea(absent()) .shipType(absent()).speedMetresPerSecond(Optional.of(0.0)).widthMetres(absent()) .id(new Mmsi(0)); State nextState(final long maxTimeInterval, VesselPosition p) { // add p to byTime, tree and map Set<VesselPosition> newByTime = add(byTimeAndId, p); RTree<VesselPosition, Point> newTree = tree.add(p, geometry(p)); Map<Identifier, Set<VesselPosition>> newMap = new HashMap<>(map); addToMap(newMap, p); // remove expired vessel positions if (counter % 10000 == 0) { // remove items from the map long t = System.currentTimeMillis(); P3<Set<VesselPosition>, Option<VesselPosition>, Set<VesselPosition>> split = newByTime .split(BUILDER.time(p.time() - maxTimeInterval).build()); Set<VesselPosition> removeThese = split._1(); // remove items from the byTime set newByTime = split._3(); ListMultimap<Identifier, VesselPosition> lists = ArrayListMultimap.create(); for (VesselPosition vp : removeThese) { lists.put(vp.id(), vp); } int count = 0; for (Collection<VesselPosition> list : lists.asMap().values()) { Identifier id = list.iterator().next().id(); Set<VesselPosition> removals = Set.iterableSet(ordering, list); Set<VesselPosition> set = newMap.get(id); set = set.minus(removals); if (set.size() == 0) newMap.remove(id); else newMap.put(id, set); count += list.size(); } // log.info("removed " + count + " from map"); // remove items from the tree // log.info("removing from tree"); for (VesselPosition vp : removeThese) { newTree = newTree.delete(vp, geometry(vp)); } // log.info("removed from tree"); if (newTree.size() != newByTime.size()) throw new RuntimeException("unexpected"); t = System.currentTimeMillis() - t; log.info("removed " + count + " in " + t + "ms"); } return new State(newMap, newByTime, newTree, Optional.of(p), counter + 1); } private static void addToMap(Map<Identifier, Set<VesselPosition>> map, VesselPosition p) { Optional<Set<VesselPosition>> existing = Optional.fromNullable(map.get(p.id())); if (existing.isPresent()) map.put(p.id(), add(existing.get(), p)); else map.put(p.id(), EMPTY.insert(p)); } private static Point geometry(VesselPosition p) { return Geometries.point(p.lon(), p.lat()); } public int mapSize() { return map.size(); } private static final Equal<VesselPosition> EQUAL_ID = Equal .equal(new F<VesselPosition, F<VesselPosition, Boolean>>() { @Override public F<VesselPosition, Boolean> f(final VesselPosition a) { return new F<VesselPosition, Boolean>() { @Override public Boolean f(VesselPosition b) { return a.id().equals(b.id()); } }; } }); private static <A> Ord<A> toOrdering(final Comparator<A> comparator) { return Ord.ord(new F<A, F<A, Ordering>>() { @Override public F<A, Ordering> f(final A a1) { return new F<A, Ordering>() { @Override public Ordering f(final A a2) { final int x = comparator.compare(a1, a2); return x < 0 ? Ordering.LT : x == 0 ? Ordering.EQ : Ordering.GT; } }; } }); } }