/** Copyright 2015 Tim Engler, Rareventure LLC This file is part of Tiny Travel Tracker. Tiny Travel Tracker is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Tiny Travel Tracker 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. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>. */ package com.rareventure.gps2.database.cache; import java.util.ArrayList; import android.util.Log; import com.rareventure.android.Util; import com.rareventure.android.database.Cache; import com.rareventure.android.encryption.EncryptedRow; import com.rareventure.gps2.CacheException; import com.rareventure.gps2.GTG; import com.rareventure.gps2.database.TAssert; import com.rareventure.gps2.database.cachecreator.ViewNode; public class TimeTree extends EncryptedRow { public static int NUM_NODES = 4; public static final Column SUB_NODES = new Column("SUB_NODES", (Integer.SIZE>>3) * NUM_NODES); //used for tree balancing. total number of sub nodes (including descendents) including self public static final Column TOTAL_SUB_NODE_COUNT = new Column("TOTAL_SUB_NODE_COUNT", Integer.class); //maxtime of the points within the node. Note that this actually is the earliest gps //point after the user left the node. public static final Column MAX_TIME_SECS = new Column("MAX_TIME_SECS", Integer.class); //min time of the points within the node. Note that this actually is the latest gps //point before the user entered the node + 1 second. We use this range because it helps us //calculate the speed through the node (time over distance) easier for bottom //level aps. I believe it also has something to do with drawing lines, but this //may no longer be true due to prev_ap_fk and next_ap_fk public static final Column MIN_TIME_SECS = new Column("MIN_TIME_SECS", Integer.class); //max gap in times for the sub nodes public static final Column TIME_JUMP_SECS = new Column("TIME_JUMP_SECS", Integer.class); /** * These are the previous and next ap at the same depth as the current ap */ public static final Column PREV_AP_FK = new Column("PREV_AP_FK", Integer.class); public static final Column NEXT_AP_FK = new Column("NEXT_AP_FK", Integer.class); //this is a double for precision, since we have to add up all the distances of the sub //time trees //PERF: we only need this for the bottom level tt's (as well as max elev and min elev) //so we could use some sort of union structure... this is // oppositely true for sub_nodes. total_sub_node_count could be used to then identify // a bottom level time tree public static final Column PREV_AND_CURR_DIST_M = new Column("PREV_AND_CURR_DIST_M", Double.class); // public static final Column MAX_ELEV = new Column("MAX_ELEV", Integer.class); // public static final Column MIN_ELEV = new Column("MIN_ELEV", Integer.class); // number of gps points contained by the time tree public static final Column NUM_POINTS = new Column("NUM_POINTS", Integer.class); public static final Column[] COLUMNS = new Column[] { TOTAL_SUB_NODE_COUNT, SUB_NODES, MIN_TIME_SECS, MAX_TIME_SECS, TIME_JUMP_SECS, PREV_AP_FK, NEXT_AP_FK, PREV_AND_CURR_DIST_M, NUM_POINTS // , MAX_ELEV, MIN_ELEV }; public static final int DATA_LENGTH = EncryptedRow.figurePosAndSizeForColumns(COLUMNS) ; private TimeTree tt; public TimeTree() { } /** * Initialize a timetree for insertion. Note we don't do this in the constructor because when * we decrypt, the data2 length will be longer than DATA_LENGTH (due to the way decryption works) * so we don't want to create data2 twice when we read from the db */ public void initNewTimeTree() { data2 = new byte [DATA_LENGTH]; for(int i = 0; i < NUM_NODES; i++) { setSubNodeFk(i, Integer.MIN_VALUE); } } public int getDataLength() { return DATA_LENGTH; } public ArrayList<TimeTree> getAllBottomLevelTimeTrees() { ArrayList<TimeTree> al = new ArrayList<TimeTree>(); getAllBottomLevelTimeTrees(al); return al; } public void getAllBottomLevelTimeTrees(ArrayList<TimeTree> al) { int i; for(i = 0; i < NUM_NODES; i++) { TimeTree child = getSubNode(i); if(child == null) break; child.getAllBottomLevelTimeTrees(al); } if(i == 0) al.add(this); } public int getNearestTimePoint(int timeSecs, boolean latestPreviousOrEarliestNext) { if(latestPreviousOrEarliestNext) { if(timeSecs >= getMaxTimeSecs()) return getMaxTimeSecs() - 1; else if (timeSecs < getMinTimeSecs()) throw new CacheException("asked for latest previous but we are completely after time, " +timeSecs+", this is "+this); } else { if(timeSecs < getMinTimeSecs()) return getMinTimeSecs(); else if (timeSecs >= getMaxTimeSecs()) throw new CacheException("asked for earliest next but we are completely before time, " +timeSecs+", this is "+this); } //if there are no sub nodes at all if(getSubNodeFk(0) == Integer.MIN_VALUE) { //then we contain the exact value return timeSecs; } int lastEndTime = 0; for(int i = 0; i < NUM_NODES; i++) { TimeTree subTT = getSubNode(i); //if its before the subtt if(timeSecs < subTT.getMinTimeSecs()) { if(latestPreviousOrEarliestNext) { if(i == 0) throw new CacheException("first child isn't aligned with parent," + " this is "+this+", subTT is "+subTT); return lastEndTime; } return subTT.getMinTimeSecs(); } //if the point is wihtin the sub tt if(timeSecs < subTT.getMaxTimeSecs()) return subTT.getNearestTimePoint(timeSecs, latestPreviousOrEarliestNext); lastEndTime = subTT.getMaxTimeSecs(); } throw new CacheException("last child isn't aligned with parent," + " this is "+this+", subTT is "+getSubNode(NUM_NODES-1)); } public int getMinTimeSecs() { return getInt(MIN_TIME_SECS); } public int getMaxTimeSecs() { return getInt(MAX_TIME_SECS); } public Cache getCache() { return (Cache) GTG.ttCache; } /** * Adds a segment to the time tree and updates NUM_POINTS for the addition of one gps point */ public TimeTree addSegmentForPoint(int startTimeSec, int endTimeSec, int prevApId, double dist) { // if(startTimeSec < getMaxTimeSecs()) // TAssert.fail("received a segment that is earlier than the latest one! Not acceptable: "+this+" "+startTimeSec); // //first check if we can just extend the time tree for the segment if(getMaxTimeSecs() == startTimeSec) { extendTimeTree(endTimeSec, dist, true); return this; } //otherwise we will have to create a new timetree else { return addTimeTree(createTimeTreeForGpsPoint(startTimeSec, endTimeSec, prevApId, dist)); } } public static TimeTree createTimeTreeForGpsPoint(int startTimeSec, int endTimeSec, int prevApId, double dist) { TimeTree tt = GTG.ttCache.newRow(); tt.initNewTimeTree(); tt.setMinTimeSecs(startTimeSec); tt.setMaxTimeSecs(endTimeSec); tt.setTimeJumpSecs(0); tt.setTotalSubNodeCount(1); tt.setInt(PREV_AP_FK.pos, prevApId); tt.setInt(NEXT_AP_FK.pos, Integer.MIN_VALUE); tt.setNumPoints(1); tt.setPrevAndCurrDistM(dist); // if(startTimeSec >= endTimeSec) // throw new IllegalStateException("time tree of zero or less length has been added, tt is "+tt); return tt; } private void setMinTimeSecs(int startTimeSec) { setInt(MIN_TIME_SECS.pos, startTimeSec); // if(getMaxTimeSecs() > 0 && getMinTimeSecs() >= getMaxTimeSecs()) // throw new IllegalStateException("what? "+this); } private void setMaxTimeSecs(int endTimeSec) { // if(getMinTimeSecs() >= endTimeSec) // throw new IllegalStateException("what? "+this); setInt(MAX_TIME_SECS.pos, endTimeSec); } private void setTimeJumpSecs(int timeJumpSec) { setInt(TIME_JUMP_SECS.pos, timeJumpSec); } public int getTimeJumpSecs() { return getInt(TIME_JUMP_SECS); } // private void setMaxElev(int val) { // setInt(MAX_ELEV.pos, val); // } // // // private int getMaxElev() { // return getInt(MAX_ELEV); // } // // private void setMinElev(int val) { // setInt(MIN_ELEV.pos, val); // } // // // private int getMinElev() { // return getInt(MIN_ELEV); // } public void setPrevAndCurrDistM(double val) { setDouble(PREV_AND_CURR_DIST_M.pos, val); } public double getPrevAndCurrDistM() { return getDouble(PREV_AND_CURR_DIST_M ); } public void extendTimeTree(int endTimeSec, double dist, boolean containsNewPoint) { setMaxTimeSecs(endTimeSec); setPrevAndCurrDistM(getPrevAndCurrDistM()+dist); if(containsNewPoint) setNumPoints(getNumPoints()+1); TimeTree lastChild = getLastChild(); if(lastChild != null) lastChild.extendTimeTree(endTimeSec, dist, containsNewPoint); } private TimeTree getLastChild() { for(int i = NUM_NODES-1; i >= 0; i--) { TimeTree subNode = getSubNode(i); if(subNode != null) return subNode; } return null; } /** * Adds a time tree to the end of the tree. It is assumed that tt * starts after the current time tree ends * @param tt * @return parent of this and tt (may be this) */ private TimeTree addTimeTree(TimeTree tt) { if(tt.getMinTimeSecs() < this.getMaxTimeSecs()) throw new CacheException("trying to add a tt that is before the end of this one, this: "+this+", tt is "+tt); //since we're always adding to the end of the tree, //we always follow the same pattern, so that all kids have equal depth in the long run int index = chooseSubNodeOrCurrNodeToAddTo(); //if all the kids have the same depth if(index == NUM_NODES) { //create a parent covering us and tt return createTimeTreeForKids(this, tt); } else { //the children are different lengths, so add it to the shortest child (will always be the end guy) //if there is no child there yet at all if(getSubNode(index) == null) { setSubNodeFk(index, tt.id); } else { //there is, so add the time tree to it setSubNodeFk(index, getSubNode(index).addTimeTree(tt).id); } //in any case, it becomes a descendent of us, so our sub node count increases by its subnode count setTotalSubNodeCount(getTotalSubNodeCount() + tt.getTotalSubNodeCount()); setNumPoints(tt.getNumPoints() + getNumPoints()); setTimeJumpSecs(Util.maxAll(getTimeJumpSecs(),tt.getTimeJumpSecs(),tt.getMinTimeSecs() - getMaxTimeSecs())); setMaxTimeSecs(tt.getMaxTimeSecs()); //our distance traveled also gets updated by its distance setPrevAndCurrDistM(getPrevAndCurrDistM() + tt.getPrevAndCurrDistM()); //since we're extending our time tree to the child one, //the next ap of the child should be the next ap of us. //keep in mind that the ap for both are the same, we are //getting more specific in time as we go down the time tree //but the ap remains the same. setNextApId(tt.getNextApId()); return this; } } private int getNumPoints() { return getInt(NUM_POINTS.pos); } private void setNumPoints(int numPoints) { setInt(NUM_POINTS.pos, numPoints); } private static TimeTree createTimeTreeForKids(TimeTree t1, TimeTree t2) { TimeTree tt = GTG.ttCache.newRow(); tt.initNewTimeTree(); tt.setSubNodeFk(0,t1.id); tt.setSubNodeFk(1,t2.id); tt.setMinTimeSecs(t1.getMinTimeSecs()); tt.setMaxTimeSecs(t2.getMaxTimeSecs()); tt.setTimeJumpSecs(Util.maxAll(t1.getTimeJumpSecs(),t2.getTimeJumpSecs(),t2.getMinTimeSecs() - t1.getMaxTimeSecs())); tt.setTotalSubNodeCount(1+t1.getTotalSubNodeCount() + t2.getTotalSubNodeCount()); tt.setNumPoints(t1.getNumPoints()+t2.getNumPoints()); tt.setPrevAndCurrDistM(t1.getPrevAndCurrDistM() + t2.getPrevAndCurrDistM()); tt.setInt(PREV_AP_FK.pos, t1.getPrevApId()); tt.setInt(NEXT_AP_FK.pos, t2.getNextApId()); return tt; } public int getPrevApId() { return getInt(PREV_AP_FK); } public int getNextApId() { return getInt(NEXT_AP_FK); } private void setTotalSubNodeCount(int i) { setInt(TOTAL_SUB_NODE_COUNT.pos, i); } private void setSubNodeFk(int index, int fk) { setInt(TimeTree.SUB_NODES.pos + (Integer.SIZE >> 3) * index, fk); } /** * Chooses either to add a point to the latest sub node, * a new empty sub node, or to convert the parent and the new node as a sub node * and create a parent for it in place of the current node * * @return index of subnode to add to. If index points to an empty subnode slot, * a sub node should be created. if NUM_NODES is returned, the parent should * be set as a sibling of the new node and a new parent should be created */ private int chooseSubNodeOrCurrNodeToAddTo() { for(int i = NUM_NODES-1; i >= 1; i--) { if(getSubNodeFk(i) != Integer.MIN_VALUE) { if(getSubNode(i).getTotalSubNodeCount() < getSubNode(i-1).getTotalSubNodeCount()) return i; //if the last node has last sub nodes, we add to it until its equal with the current one else //if they are equal { if(getSubNode(i).getTotalSubNodeCount() < getSubNode(i-1).getTotalSubNodeCount()) TAssert.fail("last node has more subnodes that previous node, last node: "+getSubNode(i)+ ", previous node: "+getSubNode(i-1)); //in this case we add another child if there is room, or we make the parent a sibling of a node new with //a new parent //(if we return NUM_NODES the caller should make the parent a sibling) return i+1; } } } return NUM_NODES; } private int getTotalSubNodeCount() { return getInt(TOTAL_SUB_NODE_COUNT); } public TimeTree getSubNode(int i) { int fk = getSubNodeFk(i); if(fk != Integer.MIN_VALUE) return GTG.ttCache.getRow(fk); return null; } public int getSubNodeFk(int index) { return getInt(SUB_NODES.pos + (Integer.SIZE>>3) * index); } /** * * @param startTimeSecs * @param endTimeSecs * @return true if intersects. False if for sure, like, doesn't. null if the children might */ private IntersectsTimeStatus intersectsTimeNoRecursive(int startTimeSecs, int endTimeSecs) { if(endTimeSecs <= getMinTimeSecs()) return IntersectsTimeStatus.RANGE_IS_BEFORE; if(startTimeSecs >= getMaxTimeSecs()) return IntersectsTimeStatus.RANGE_IS_AFTER; //if it crosses the start or the end of the time tree, then it automatically intersects if(startTimeSecs <= getMinTimeSecs()) { if(endTimeSecs >= getMaxTimeSecs()) return IntersectsTimeStatus.ENVELOPS_TIME_TREE; return IntersectsTimeStatus.RANGE_OVERLAPS_MIN_TIME; } if(endTimeSecs >= getMaxTimeSecs()) return IntersectsTimeStatus.RANGE_OVERLAPS_MAX_TIME; //else it's in the center of this sub tt //if we aren't higher than the time jump, then we overlap if(endTimeSecs - startTimeSecs >= getTimeJumpSecs()) { return IntersectsTimeStatus.INTERSECTS_OVER_TIME_JUMP; } return null; } public static int hackTimesLookingUpTimeTrees; public static IntersectsTimeStatus intersectsTime(TimeTree tt, int startTimeSecs, int endTimeSecs) { //Log.d("GPS","intersectsTime, startTime="+startTimeSecs+", endTime="+endTimeSecs+" tt "+tt); { IntersectsTimeStatus ttIntersects = tt.intersectsTimeNoRecursive(startTimeSecs, endTimeSecs); if(ttIntersects != null) { //Log.d("GPS", "returning off the bat "+ttIntersects); return ttIntersects; } } //note, now we know that the range is internal while(true) { hackTimesLookingUpTimeTrees++; TimeTree subTT = null; //PERF: we could probably start from the center instead for(int i=0; i < NUM_NODES; i++) { int fk = tt.getSubNodeFk(i); //for time trees, fk's are filled from the start to a point. Then there are nulls if(fk == Integer.MIN_VALUE) { ArrayList<TimeTree> res = new ArrayList(); for(int j = 0; j < NUM_NODES; j++) res.add(tt.getSubNode(j)); TAssert.fail("tt claims to envelope time range: "+startTimeSecs+", "+endTimeSecs+ ", but none of its children overlap it (short children) tt: "+tt +" children: "+res); //following was commented out and replaced with assert above: //return IntersectsTimeStatus.EMPTY_WITHIN; } subTT = GTG.ttCache.getRow(fk); //Log.d("GPS","intersectsTime, subTT "+subTT); IntersectsTimeStatus intersects = subTT.intersectsTimeNoRecursive(startTimeSecs, endTimeSecs); //if we have an idea if(intersects != null) { if(intersects.overlaps) //note, we could be more specific here and indicate whether start time or end time // intersects the child { //Log.d("GPS", "returning interesects within"); return IntersectsTimeStatus.INTERSECTS_WITHIN; } else if (intersects == IntersectsTimeStatus.RANGE_IS_BEFORE) { //Log.d("GPS", "returning empty within"); return IntersectsTimeStatus.EMPTY_WITHIN; } else //its past this nodes end point so we go on to the next one continue; } //it may be contained in the children of this child tt = subTT; //rerun the above with subTT... //note that we are only checking one child. This is because we only need to check it //if the time interval is completely enveloped by the child (in which case, it couldn't // be also contained by any other time tree. break; } //if we ran past the edge of the node if(subTT != tt) { ArrayList<TimeTree> res = new ArrayList(); for(int i = 0; i < NUM_NODES; i++) res.add(tt.getSubNode(i)); TAssert.fail("tt claims to envelope time range, but none of its children overlap it tt: "+tt +" children: "+res); } }// while checking the tree } /** * * @param range Range in seconds, will be modified to reflex the range actually covered. * Note that this will be the cut range. * @param timeFuzzinessPerc the amount of leeway we have when returning a start and end time. * This allows us to save time and not go all the way down the tree. * Note, we do this instead of a range because there is no real way to determine beforehand * what we'll find in the current window. For example, if the time range is 2 years, but we * were only in the particular ap for a few minutes, then the time has to be very specific, * but we wouldn't know this before we looked * Note that having a timeFuzzinessPerc does not indicate that we are displaying ap's when * they don't have time tree in the range. The max time jump must be under the range time * for the time tree to be used. (ex if the range was for 2 months, and the time jump for * 1 month, there is no way the range would fit inside the time jump area, so we know for * sure the area panel was visited) * * * **/ public static void findOverlappingRange(int [] range, TimeTree tt,float timeFuzzinessPerc) { TimeTree lt = tt, rt = tt; //set the initial fuzziness based on tt. int leftFuzziness = handleLeftOverlappingRange(range, lt), rightFuzziness = handleRightOverlappingRange(range, rt); //while we are still too fuzzy while(range[1] > range[0] && (leftFuzziness +rightFuzziness) > (range[1] - range[0]) * timeFuzzinessPerc) { if(leftFuzziness >= rightFuzziness) { lt = getSubTree(lt, range[0], false); //this updates range, and returns fuzziness leftFuzziness = handleLeftOverlappingRange(range, lt); } else { rt = getSubTree(rt, range[1], true); //this updates range, and returns fuzziness rightFuzziness = handleRightOverlappingRange(range, rt); } } range[0] = Math.max(lt.calcTimeRangeCutStart(), range[0]); range[1] = Math.min(range[1],rt.calcTimeRangeCutEnd()); } /** * Returns the subtree which intersects the timeSec, or if the timeSec * falls within a gap, the subtree to the left or right. It is assumed * that timeSec is within tt (and there are at least two kids, which should * always be true) * @param timeSec * @param left * @return */ private static TimeTree getSubTree(TimeTree tt, int timeSec, boolean left) { if(tt.getSubNode(0) == null) { /* ttt_installer:remove_line */Log.d("wha?",tt.toString()); } if(timeSec < tt.getSubNode(0).getMaxTimeSecs()) return tt.getSubNode(0); for(int i = 1; i < NUM_NODES; i++) { //if we want to return the left one in case of a gap, and we are before min, //(we already know were after max of the left one) //return the one beforehand if(left && timeSec < tt.getSubNode(i).getMinTimeSecs()) return tt.getSubNode(i-1); //otherwise if we are before max, we are either right, or within the tt itself if(timeSec < tt.getSubNode(i).getMaxTimeSecs()) return tt.getSubNode(i); } TAssert.fail("sub trees dont extend to timeSec, tt: "+tt+" timeSec: "+timeSec); return null; } /** * * @param range * @param lt * @return the amount of leeway that the range could have on the left side */ private static int handleLeftOverlappingRange(int[] range, TimeTree lt) { //if we are outside if(range[0] <= lt.getMinTimeSecs()) { range[0] = lt.getMinTimeSecs(); return 0;//no more fuzziness } //the most amount of fuzziness we could have would be all the way //from the location to the end of the time tree, or the max space //between children return Math.min(lt.getTimeJumpSecs(), lt.getMaxTimeSecs() - range[0]); } /** * * @param range * @param rt * @return the amount of leeway that the range could have on the right side */ private static int handleRightOverlappingRange(int[] range, TimeTree rt) { if(range[1] >= rt.getMaxTimeSecs()) { range[1] = rt.getMaxTimeSecs(); return 0; } return Math.min(range[1] - rt.getMinTimeSecs(), rt.getTimeJumpSecs()); } public String toString() { return "TimeTree(id="+id+",minTimeSecs="+getMinTimeSecs()+",maxTimeSecs="+getMaxTimeSecs() +",timeJumpSecs="+getTimeJumpSecs()+",totalSubNodeCount="+getTotalSubNodeCount()+"dist="+getPrevAndCurrDistM()+",isBottom?=" +(getSubNodeFk(0)==Integer.MIN_VALUE)+")"; } public static enum IntersectsTimeStatus { //the stb is within the area panels time interval //but doesn't overlap any time interval of a segment //contained within it's descendents EMPTY_WITHIN(false), //same as EMPTY_WITHIN, except the STB does intersect //with a segment contained within the descendents INTERSECTS_WITHIN(true), //the stb is completely before the area panel RANGE_IS_BEFORE(false), //the stb is completely after the area panel RANGE_IS_AFTER(false), RANGE_OVERLAPS_MIN_TIME(true), RANGE_OVERLAPS_MAX_TIME(true), ENVELOPS_TIME_TREE(true) , INTERSECTS_OVER_TIME_JUMP(true) //this means that the time range //asked for is larger than the largest gap within the ap, so the //ap must be on ; public final boolean overlaps; private IntersectsTimeStatus(boolean overlaps) { this.overlaps = overlaps; } } public AreaPanel getPrevAp() { int fk = getPrevApId(); if(fk == Integer.MIN_VALUE) return null; return GTG.apCache.getRow(fk); } public AreaPanel getNextAp() { int fk = getNextApId(); if(fk == Integer.MIN_VALUE) return null; return GTG.apCache.getRow(fk); } public void setNextApId(int id) { setInt(NEXT_AP_FK.pos, id); } /** * Returns the time tree with the minimum min * time that is greater than timeSec, unless * a time tree encompasses timeSec (with no gaps), in which * case the zero time jump lowest leaf time * tree that does encompass it will be returned */ public TimeTree getEncompassigTimeTreeOrMinTimeTreeAfterTime(int timeSec, boolean alwaysBottomLevel) { //if the time tree is before the time completely, if(getMaxTimeSecs() <= timeSec) return null; TimeTree tt = this; for(;;) { if(alwaysBottomLevel && tt.getMinTimeSecs() >= timeSec) { return tt; } //if we've reached the bottom time tree //and we are encompassing the time if(tt.getSubNodeFk(0) == Integer.MIN_VALUE) return tt; int i; for(i = 0; i < NUM_NODES; i++) { //we don't check for null here because we should never get past the max child TimeTree child = tt.getSubNode(i); if(child.getMaxTimeSecs() > timeSec) { tt = child; break; } } if(i == NUM_NODES) throw new CacheException("Child tt's don't extend to parent t "+tt+" " + tt.getSubNode(i-1)); } } /** * Returns bottom level time tree encompassing the time * @param timeSec * @return */ public TimeTree getBottomLevelEncompassigTimeTree(int timeSec) { //if the time tree is before the time completely, if(getMaxTimeSecs() <= timeSec) return null; TimeTree tt = this; for(;;) { if(tt.getMinTimeSecs() > timeSec) { return null; } //if we've reached the bottom time tree //and we are encompassing the time if(tt.getSubNodeFk(0) == Integer.MIN_VALUE) return tt; int i; for(i = 0; i < NUM_NODES; i++) { //we don't check for null here because we should never get past the max child TimeTree child = tt.getSubNode(i); if(child.getMaxTimeSecs() > timeSec) { tt = child; break; } } if(i == NUM_NODES) throw new CacheException("Child tt's don't extend to parent t "+tt+" " + tt.getSubNode(i-1)); } } /** * Returns the time tree with the maximum max * time that is less than timeSec, unless * a time tree encompasses timeSec, in which * case the zero time jump lowest leaf time * tree that does encompass it will be returned */ public TimeTree getEncompassigTimeTreeOrMaxTimeTreeBeforeTime(int timeSec, boolean alwaysBottomLevel) { //if the time tree is after the time completely, if(getMinTimeSecs() >= timeSec) return null; TimeTree tt = this; for(;;) { if(!alwaysBottomLevel && tt.getMaxTimeSecs() <= timeSec) { return tt; } //if we've reached the bottom time tree //and we are encompassing the time if(tt.getSubNodeFk(0) == Integer.MIN_VALUE) return tt; int i; for(i = NUM_NODES-1; i >= 0; i--) { TimeTree child = tt.getSubNode(i); //since we're starting from the farthest end //and going in, we may get a null or two before //the actual children if(child == null) continue; if(child.getMinTimeSecs() < timeSec) { tt = child; break; } } if(i < 0) throw new CacheException("Child tt's don't extend to parent t " + tt+" "+ tt.getSubNode(i+1)); } } public void setNextApIdForThisAndAllChildren(int id) { tt = this; for(;;) { tt.setNextApId(id); int i; for(i = NUM_NODES-1; i >= 0; i--) { TimeTree child = tt.getSubNode(i); //since we're starting from the farthest end //and going in, we may get a null or two before //the actual children if(child == null) continue; tt = child; break; } if(i == -1) break; } } public TimeTree getMinTimeTreeAfterTime(int timeSec) { //if the time tree is before the time completely, if(getMaxTimeSecs() <= timeSec) return null; if(getMinTimeSecs() >= timeSec) return this; TimeTree tt = this; TimeTree bestTt = null; for(;;) { if(tt.getSubNodeFk(0) == Integer.MIN_VALUE) return bestTt; int i; for(i = NUM_NODES-1; i>= 0; i--) { TimeTree childTt = tt.getSubNode(i); if(childTt == null) continue; if(childTt.getMinTimeSecs() >= timeSec && (bestTt == null || childTt.getMinTimeSecs() < bestTt.getMinTimeSecs())) { bestTt = childTt; } else if(childTt.getMaxTimeSecs() <= timeSec) { return bestTt; } else //we're encompassing { tt = childTt; break; } } if(i == -1) throw new CacheException("Child tt doesn't start at parent t "+tt+" " + tt.getSubNode(0)); } } public int getTimeSec() { return getMaxTimeSecs() - getMinTimeSecs(); } public int calcTimeRangeCutEnd() { AreaPanel np = getNextAp(); if(np != null) { TimeTree ntt = np.getTimeTree().getEncompassigTimeTreeOrMaxTimeTreeBeforeTime(getMaxTimeSecs(), true); return ntt.getMinTimeSecs(); } else return getMaxTimeSecs(); } public int calcTimeRangeCutStart() { AreaPanel pp = getPrevAp(); if(pp != null) { TimeTree ptt = pp.getTimeTree().getEncompassigTimeTreeOrMinTimeTreeAfterTime(getMinTimeSecs(), true); return ptt.getMaxTimeSecs(); } else return getMinTimeSecs(); } public int getPrevApPrevTtEndTime() { AreaPanel ap = getPrevAp(); if(ap == null) return Integer.MIN_VALUE; return ap.getTimeTree().getEncompassigTimeTreeOrMaxTimeTreeBeforeTime(getMinTimeSecs(), false).getMaxTimeSecs(); } public int getNextApNextTtStartTime() { AreaPanel ap = getNextAp(); if(ap == null) return Integer.MIN_VALUE; return ap.getTimeTree().getEncompassigTimeTreeOrMinTimeTreeAfterTime(getMaxTimeSecs(), false).getMinTimeSecs(); } public boolean isBottomLevel() { return getSubNodeFk(0) == Integer.MIN_VALUE; } /** * True if the cut time (the actual gps points) overlap the given time */ public boolean isCutTimeOverlap(int startTimeSec, int endTimeSec) { // at the bottom level there are areas that are covered and gaps between // we return true if the start and end are in different gaps or // the start or end intersect with a covered area // By covered area, I mean a time tree using the cut start and cut end // values //note that we don't set bottom to true, since the only time we won't be at the //bottom is when the tt is after the start time (and the end time in the second // statement). In this case we act the same regardless if the time tree is at the //bottom or not TimeTree startTt = getEncompassigTimeTreeOrMinTimeTreeAfterTime(startTimeSec, false); TimeTree endTt = getEncompassigTimeTreeOrMinTimeTreeAfterTime(endTimeSec, false); //if the minimum start and end tt are different, they must overlap the ap, since //there must be interleaving gps points between the start time and end time in that case if(startTt != endTt && //this is to check for cases where the start time does not overlap, but the //end time does (or vice versa) but it doesn't overlap the cut time (which // we haven't determined yet). In this case the startTt and endTt could be // different but they represent the same time (startTt == null || endTt == null || startTt.getMinTimeSecs() == endTt.getMinTimeSecs() || startTt.getMaxTimeSecs() == endTt.getMaxTimeSecs())) return true; //if null the start time and end time extends beyond the end of the time tree if(startTt == null) return false; int startCutSec = startTt.calcTimeRangeCutStart(); if(startTimeSec < startCutSec) { //both start and end are before the tt if(endTimeSec <= startCutSec) return false; //else the end time is either intersecting or after the tt return true; } //startTimeSec >= startCutSec int endCutSec = startTt.calcTimeRangeCutEnd(); //(note that we know already that endtime isn't beyond the end of the tt // because if it was a different endTt would have been returned) //the start time intersects the tt if(startTimeSec < endCutSec) return true; //otherwise both the start time and end time must be after the tt return false; } }