/**
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.cachecreator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import com.rareventure.gps2.GTG;
import com.rareventure.gps2.database.TAssert;
import com.rareventure.gps2.database.cache.AreaPanel;
import com.rareventure.gps2.database.cache.AreaPanelSpaceTimeBox;
import com.rareventure.gps2.database.cache.TimeTree;
import com.rareventure.gps2.reviewer.map.GpsTrailerOverlay;
import com.rareventure.gps2.reviewer.map.ViewLine;
import com.rareventure.gps2.reviewer.map.sas.Path;
/**
* represents nodes that are visible according to an stbox which may or may not
* be the current one.
*
* View Nodes can either be SET or EMPTY, and this is according to the stbox
* in their posession.. all view nodes will have an AreaPanel. If an AreaPanel
* does not exist for a particular location visible from an stbox, a
* null child will be created for it (ie there will be no view nodes with null
* area panels)
*
*
*/
public class ViewNode {
/**
* The minimum time jump for a gap to be worthy to chase down a line for as related to
* ap size
*/
//10 hours for a depth a quarter the size of the world (~5000 to 10000 miles depending on
// how close you are to the poles)
//WARNING depends on size 2 for SUB_AREA_PANELS_PER_SIDE
private static final float MIN_TIME_JUMP_SECS_FOR_LINES_TO_AP_SIZE = 72000f / (1<< 24);
/**
* The max number of time trees to sample when figuring out lines
*/
private static final int MAX_TIME_TREE_SAMPLES = 5;
private static final Comparator<TimeTree> TIME_TREE_TIME_SEC_COMPARATOR = new Comparator<TimeTree>() {
@Override
public int compare(TimeTree lhs, TimeTree rhs) {
return lhs.getTimeSec() - rhs.getTimeSec();
}
};
private static final int MAX_CALCULATED_LINES_PER_ROUND = 128;
private static final int MIN_CALCULATED_LINES_PER_ROUND = 4;
public String toString() {
return "ViewNode(status=" + status + ",stBox=" + stBox + ",ap=" + ap()
+ ",childrenNull?=" + (children == null ? "Y" : "N")
+ ",dirtyDescendents="+dirtyDescendents + ")";
}
// status of View node
public static enum VNStatus {
// is not in the stb and does not contain any sub area panels within the
// stb
EMPTY,
// intersects the stb. If a view node is in this state, children[] will be set
SET
};
public VNStatus status;
// the space time box this view node has been updated to. always present
// unless vnstatus is null
public AreaPanelSpaceTimeBox stBox;
public int apId;
public AreaPanel ap()
{
return GTG.apCache.getRow(apId);
}
/**
//children of viewnode. If set, always an array of length
//AreaPanel.NUM_SUB_PANELS. if a particular location doesn't
//have an area panel at all (regardless of time), the view panel will be null.
//children will also be null if VNStatus is EMPTY or null, or we've reached
//min depth
*/
public ViewNode[] children;
/**
// the count of all descendents of current node that are dirty, including
// itself with respect to the latest stBox (not the viewnodes stBox)
// this includes view nodes with null status
*/
public int dirtyDescendents;
/**
// the range of time that the stbox overlaps the areapanel's time(s)
// just a length of 2 array.. ie. min and max
// note, this is needed so that we can display the total range of time
// covered within the timeview (as the rainbow bar). Specifically, this is
// why the lower bound is needed.
// also note this is calculated with some fuzziness present, ie. the range
// will not be exact, since it only deals with colors. Keep this in mind
// if using for other purposes. Take a look at TimeTree.findOverlappingRange
// for more information on this
*/
public int[] overlappingRange;
/**
* When lines are calculated for view nodes, we start by doing the start and
* end times, then we look inside and do the ends of the largest time tree.
* This is to hopefully pick up long lines to far away places first, since
* those are off screen and we'll only have one chance to pick them up.
*
* Note that we do not use timejump for this purpose, because timejump
* will include cases where we go from A -> B -> C -> B -> A where C is a far
* away place and A and B is not. The point is that A has a larger
* timejump than A, although the line from A to B is short. And you can
* imagine A's and B's ad infinitum.
*/
public int largestTimeTreeLengthForUncreatedLines = Integer.MAX_VALUE;
public static ViewNode createHeadViewNode() {
ViewNode head = new ViewNode();
if(GTG.apCache.isDbFilled())
{
head.status = null;
head.dirtyDescendents = 1;
head.apId = GTG.apCache.TOP_ROW_ID;
return head;
}
else //there are no points at all
{
head.status = VNStatus.EMPTY;
head.dirtyDescendents = 0;
head.apId = GTG.apCache.TOP_ROW_ID;
return head;
}
}
public ViewNode() {
}
/**
* sets the status to on for the view node ofr the current stbox. Note that the view node
* may have already been set, but for a prior stbox (which would make its status unknown
* for the current stbox)
*
* @param parentsAndCurrent
* @param newLocalStBox
* @param childrenDirty
* @param minDepth
*/
public void setSetStatus(
ArrayList<ViewNode> parentsAndCurrent,
AreaPanelSpaceTimeBox newLocalStBox, boolean childrenDirty,
int minDepth) {
AreaPanel ap = ap();
this.status = VNStatus.SET;
//update dirty descendents
int oldDirtyDescendents = this.dirtyDescendents;
if (ap.getDepth() == minDepth) {
this.dirtyDescendents = 0;
if(children != null)
TAssert.fail("why are children not null");
}
else {
//if the item was not set before, it would not have children
if(children == null)
{
createUnknownChildren();
}
else if (!childrenDirty) // if the change to the stbox wouldn't set
// the children to dirty
{
// set them all to clean if they were clean for the last stbox.
// (this will update this.dirtyDescendents)
cleanAllDescendentsIfCleanBefore(newLocalStBox, stBox, minDepth);
}
else
{
//we can only update this one
this.dirtyDescendents--;
}
}
// update the setNodesInSelfAndDescendentsCount for all the ancestors
for (ViewNode vn : parentsAndCurrent)
{
if(vn != this)
vn.dirtyDescendents += this.dirtyDescendents - oldDirtyDescendents;
}
stBox = newLocalStBox;
}
private boolean hasUnknownChild(AreaPanelSpaceTimeBox newLocalStBox) {
if(children != null)
{
for(ViewNode child : children)
{
if(child != null && child.needsProcessing(newLocalStBox))
return true;
}
}
return false;
}
/**
* Updates dirtyDescendents so that if they were clean according to parentStBox, they're clean now.
*/
private void cleanAllDescendentsIfCleanBefore(AreaPanelSpaceTimeBox newStBox,
AreaPanelSpaceTimeBox parentStBox, int minDepth) {
if (status == null || this.stBox != parentStBox)
return; // we were dirty before
//else we were clean before, so we're clean now
this.stBox = newStBox;
//update our own dirtyDescendents because we've just turned clean
this.dirtyDescendents--;
// if we were empty, then there definitely aren't any children and we
// were clean before
if (status == VNStatus.EMPTY) {
return;
}
//status is SET
if(ap().getDepth() != minDepth) //if we're at minDepth, there are no children,
//but otherwise we need to clean them
for (ViewNode child : children) {
if(child == null)
continue;
int oldChildDirtyDescendents = child.dirtyDescendents;
child.cleanAllDescendentsIfCleanBefore(newStBox, parentStBox, minDepth);
dirtyDescendents += child.dirtyDescendents - oldChildDirtyDescendents;
}
}
public void setEmptyStatus(ArrayList<ViewNode> parentsAndCurrent,
AreaPanelSpaceTimeBox newLocalStBox) {
int oldDirtyDescendents = dirtyDescendents;
for (ViewNode vn : parentsAndCurrent) {
vn.dirtyDescendents -= oldDirtyDescendents;
if (vn.dirtyDescendents < 0)
TAssert.fail("Why is dirtyDescendents negative? " + vn);
}
stBox = newLocalStBox;
// PERF we could possibly also set all the children to EMPTY as well, so
// we can cache the children if the user goes back
children = null;
status = ViewNode.VNStatus.EMPTY;
}
/**
* @param newLocalStBox
* @return overlapping range if interval overlaps, or null if it doesn't
*/
public int [] checkTimeInterval(AreaPanelSpaceTimeBox newLocalStBox) {
AreaPanel ap = ap();
// if we're out of range
// note that time tree can be null if we only created the head AreaPanel. In its initial
// state it does not have a time tree
if (ap.getTimeTree() == null || newLocalStBox.maxZ < ap.getTimeTree().getMinTimeSecs()
|| newLocalStBox.minZ >= ap.getTimeTree()
.getMaxTimeSecs())
return null;
if(newLocalStBox.pathList != null)
{
//PERF: we can remember the parents overlapping range to shorten the area that we need to check for
//the child
//we need to find the first and last occurrence of the area panel within the path list,
//for the overlapping range
int [] overlappingRange = new int[2];
int [] tempOverlappingRange = new int[2];
TimeTree rootTt = ap.getTimeTree();
int fromEndIndex;
for(fromEndIndex = newLocalStBox.pathList.size()-1; fromEndIndex >= 0; fromEndIndex--)
{
Path p = newLocalStBox.pathList.get(fromEndIndex);
tempOverlappingRange[0] = p.startTimeSec;
tempOverlappingRange[1] = p.endTimeSec;
TimeTree.findOverlappingRange(tempOverlappingRange, rootTt,
GpsTrailerOverlay.prefs.timeTreeFuzzinessPerc);
if(tempOverlappingRange[0] < tempOverlappingRange[1])
{
overlappingRange[0] = tempOverlappingRange[0];
overlappingRange[1] = tempOverlappingRange[1];
break;
}
}
//if no path has the area panel
if(overlappingRange[0] == 0)
return null;
for(int fromStartIndex = 0; fromStartIndex < fromEndIndex; fromStartIndex++)
{
Path p = newLocalStBox.pathList.get(fromStartIndex);
tempOverlappingRange[0] = p.startTimeSec;
tempOverlappingRange[1] = p.endTimeSec;
TimeTree.findOverlappingRange(tempOverlappingRange, rootTt,
GpsTrailerOverlay.prefs.timeTreeFuzzinessPerc);
if(tempOverlappingRange[0] < tempOverlappingRange[1])
{
overlappingRange[0] = tempOverlappingRange[0];
break;
}
}
return overlappingRange;
}
else
{
int [] overlappingRange = new int[] { newLocalStBox.minZ,
newLocalStBox.maxZ };
TimeTree.findOverlappingRange(overlappingRange, ap.getTimeTree(),
GpsTrailerOverlay.prefs.timeTreeFuzzinessPerc);
// Log.d("GPS","Checking time interval for "+newLocalStBox+
// ", minTimeSecs is "+ap.getTimeTree().getMinTimeSecs()+
// ", maxTimeSecs is "+ap.getTimeTree().getMaxTimeSecs()+
// ", res is "+res);
return overlappingRange[0] < overlappingRange[1] ? overlappingRange : null;
}
}
// meaningfully means that it decreased or moved in a way that would affect
// whether the area panel is on, or the min or max times of the overlapping
// range in an important way.
public boolean timeChangedMeaningfullyForSetAreaPanel(
AreaPanelSpaceTimeBox newLocalStBox) {
if (newLocalStBox.minZ <= stBox.minZ
&& newLocalStBox.maxZ >= stBox.maxZ)
return false;
// PERF: we could keep track of time tree min and max internally from
// last match
// in the status
return true;
}
public boolean xyChangedMeaningfullyWithRegardsToChildren(
AreaPanelSpaceTimeBox newLocalStBox) {
int[] truncatedOldBox = new int[4];
int[] truncatedNewBox = new int[4];
truncatedOldBox[0] = stBox.minX;
truncatedOldBox[1] = stBox.minY;
truncatedOldBox[2] = stBox.maxX;
truncatedOldBox[3] = stBox.maxY;
truncatedNewBox[0] = newLocalStBox.minX;
truncatedNewBox[1] = newLocalStBox.minY;
truncatedNewBox[2] = newLocalStBox.maxX;
truncatedNewBox[3] = newLocalStBox.maxY;
AreaPanel ap = ap();
truncateToBox(truncatedOldBox, ap.getX(), ap.getY(), ap.getMaxX(),
ap.getMaxY());
truncateToBox(truncatedNewBox, ap.getX(), ap.getY(), ap.getMaxX(),
ap.getMaxY());
if (truncatedOldBox[0] != truncatedNewBox[0]
|| truncatedOldBox[1] != truncatedNewBox[1]
|| truncatedOldBox[2] != truncatedNewBox[2]
|| truncatedOldBox[3] != truncatedNewBox[3])
return true;
return false;
}
/**
* Truncates the box specified by t to be in the border of x1, y1, x2, y2.
* If box is completely outside of x1,y1,... then it is set to 0,0,0,0. t is
* directly changed.
*
* @param t
* @param x1
* @param y1
* @param x2
* @param y2
*/
private void truncateToBox(int[] t, int x1, int y1, int x2, int y2) {
// if we're wrapping lon 0
if (t[0] > t[2]) {
// ap is in the middle and we're wrapped around lon 0 not touching
// it
if (t[0] > x2 && t[2] < x1)
t[0] = t[2] = -1;
else {
if (t[0] > x2 || t[0] < x1)
t[0] = x1;
if (t[2] > x2 || t[2] < x1)
t[2] = x1;
}
} else {
if (t[0] < x1)
t[0] = x1;
if (t[0] > x2)
t[0] = x2;
if (t[2] < x1)
t[2] = x1;
if (t[2] > x2)
t[2] = x2;
}
if (t[1] < y1)
t[1] = y1;
if (t[1] > y2)
t[1] = y2;
if (t[3] < y1)
t[3] = y1;
if (t[3] > y2)
t[3] = y2;
// if we're completely outside of the box
if (t[2] - t[0] == 0 || t[3] - t[1] == 0) {
// set everything to -1
t[0] = -1;
t[1] = -1;
t[2] = -1;
t[3] = -1;
}
}
public boolean hasUnknownChildren(AreaPanelSpaceTimeBox stBox2) {
if (children == null)
return true;
for (int i = 0; i < children.length; i++) {
if (children[i] != null && children[i].needsProcessing(stBox2))
return true;
}
return false;
}
public boolean needsProcessing(AreaPanelSpaceTimeBox newLocalStBox) {
return status == null || newLocalStBox != stBox;
}
/**
* Fills in immediate children for an unknown node. If areapanel is
* present, child becomes unk, otherwise empty.
* <p>
* WARNING: dirtyDescendents for ancestors of node are *not* updated.
* It is up to the caller to do this.
*/
public void createUnknownChildren() {
this.dirtyDescendents = 0;
//create "unknown" children
children = new ViewNode[AreaPanel.NUM_SUB_PANELS];
AreaPanel ap = ap();
for (int i = 0; i < children.length; i++) {
AreaPanel childAp = ap.getSubAreaPanel(i);
if(childAp != null) {
children[i] = new ViewNode();
children[i].status = null;
children[i].dirtyDescendents = 1;
children[i].apId = childAp.id;
this.dirtyDescendents ++;
}
}
}
/**
* Marks node and all descendents as dirty.
* If the depth of node is minDepth (the tiniest it can be to
* display in current view), will set its children to null
* @param timesToLines
*/
public int turnOnAllDirtyFlags(int minDepth) {
//for all non min depth nodes, we clear the area lines,
// so we must reset the clearLineCalcs
int myDepth = ap().getDepth();
if(myDepth != minDepth)
this.clearLineCalcs();
if (children != null) {
this.dirtyDescendents = 1;
// chop the tree at the min depth
if (minDepth == myDepth)
{
children = null;
}
else {
for (ViewNode child : children) {
if(child != null)
dirtyDescendents += child.turnOnAllDirtyFlags(minDepth);
}
}
}
else
{
//if the min depth changed, we need to see if we need to add children
//in general all set nodes that are not at min depth need children
if(status == VNStatus.SET && minDepth != ap().getDepth() && children == null)
{
//note that we add one to dirty descendents after
//createUnknownChildren() because it resets dirty descendents
//to the number of children
createUnknownChildren();
dirtyDescendents++;
}
else
this.dirtyDescendents = 1;
}
return dirtyDescendents;
}
/**
* add a newly created gps point to the top of ViewNodes. This
* will update them so they are consistent with the AreaPanels
* as long as the AreaPanels are updated for this point as well.
* <p>
* WARNING: All the view nodes must be clean before this is called. It
* does not update dirtyDescendents!
*/
public void addPointToHead(int x, int y, int lastTimeSec,
int timeSec, int minDepth)
{
if(dirtyDescendents != 0)
TAssert.fail("Don't call this method unless everything is clean");
if(stBox == null)
return;
addPointToViewNode(x, y, lastTimeSec, timeSec, minDepth);
}
/**
* Adds a point to the view node and updates itself and all children.
* True if a
* @param currGpsLocRow
* @param lastTimeSec
* @param timeSec
* @param minDepth
*/
private void addPointToViewNode(int x, int y, int lastTimeSec, int timeSec, int minDepth) {
//if its unknown, it may not have an stbox so we let it stay unknown
if(status == null)
{
TAssert.fail("Why am I unknown if everything is clean? "+this);
}
AreaPanel ap = ap();
//if the area panel doesn't contain the point at all, we're done
if(!ap.containsPoint(x, y))
return;
if(status == VNStatus.EMPTY)
{
//if were empty and the point falls outside of the stb,
//then there is nothing to do
if(stBox.maxZ < lastTimeSec)
return;
status = VNStatus.SET;
overlappingRange =
new int[] { Math.max(stBox.minZ, lastTimeSec),
Math.min(stBox.maxZ, timeSec) };
}
else
//assuming that the point time is later than any time previously known
overlappingRange[1] = Math.min(stBox.maxZ, timeSec);
//now update the children
if(ap.getDepth() != minDepth) {
if(children == null)
//create children for new set state
children = new ViewNode[AreaPanel.NUM_SUB_PANELS];
//we add the children that need to be there based on the area panels
for (int i = 0; i < children.length; i++) {
AreaPanel childAp = ap.getSubAreaPanel(i);
if(childAp == null)
continue;
if(childAp != null)
{
if(children[i] == null) {
//we set the child to empty at first, regardless if it really is or not
children[i] = new ViewNode();
children[i].status = VNStatus.EMPTY;
children[i].stBox = stBox;
children[i].apId = childAp.id;
}
//TODO 3 I'm not sure if this is entirely correct, what about
// time gaps?
//skip children that are empty (but still convert them from null to empty
// if the ap is there) A null child indicates there is no associated child ap at all.
if(childAp.outsideOfXY(stBox) ||
childAp.getEndTimeSec() <= stBox.minZ
|| childAp.getStartTimeSec() >= stBox.maxZ)
continue;
//now we add it as we did to this view node
children[i].addPointToViewNode(x, y, lastTimeSec, timeSec, minDepth);
//note, we keep looping because all the children that don't have the point
//still need to be created and marked as empty. Otherwise they will be considered
//to not even have an ap at all, and when they do get it range, they would still
// be considered empty
}
}
//note, in some cases the parent can be set and the child not. This occurs where the parent
//overlaps the stbox but the child containing the point does not
return;
}
}
/**
*
* @return the number of children that are set and do not need processing ofr
* the current stbox
*/
public int getDensity(AreaPanelSpaceTimeBox newStBox) {
int density = 0;
if(children != null)
{
for(ViewNode child : children)
{
if(child != null && child.status == VNStatus.SET
&& !child.needsProcessing(newStBox))
density++;
}
}
return density;
}
public boolean timeChangedMeaningfullyWithRegardsToChildren(
AreaPanelSpaceTimeBox newStBox) {
int oldMinT, oldMaxT;
int newMinT, newMaxT;
AreaPanel ap = ap();
oldMinT = newMinT = ap.getStartTimeSec();
oldMaxT = newMaxT = ap.getEndTimeSec();
//first truncate stbox min and max to area panel min and max for
//both old and new stbox
if(newMaxT <= newStBox.minZ || newMinT >= newStBox.maxZ)
newMinT = newMaxT = 0;
else {
newMinT = Math.max(newMinT, newStBox.minZ);
newMaxT = Math.min(newMaxT, newStBox.maxZ);
}
if(oldMaxT <= stBox.minZ || oldMinT >= stBox.maxZ)
oldMinT = oldMaxT = 0;
else {
oldMinT = Math.max(oldMinT, stBox.minZ);
oldMaxT = Math.min(oldMaxT, stBox.maxZ);
}
//if they have moved, then the children are dirty
return newMinT != oldMinT || newMaxT != oldMaxT;
}
public void calcLinesForStBox(ArrayList<TimeTree> scratchPath,
AreaPanelSpaceTimeBox apStBox, HashMap<Integer, ViewLine> startTimeToViewLine,
HashMap<Integer, ViewLine> endTimeToViewLine,
int lastNumberOfViewNodesLinesCalculatedFor) {
if(largestTimeTreeLengthForUncreatedLines == Integer.MIN_VALUE) return;
AreaPanel myAp = ap();
//if we haven't calculated for the ends yet
if(largestTimeTreeLengthForUncreatedLines == Integer.MAX_VALUE)
{
TimeTree tt = myAp.getTimeTree();
addLineForGapStart(tt, startTimeToViewLine, endTimeToViewLine);
addLineForGapEnd(tt, startTimeToViewLine, endTimeToViewLine);
largestTimeTreeLengthForUncreatedLines = tt.getTimeSec()-1;
return;
}
//we start over searching for the next time tree every time. This is because
//if we kept our place and started from where we last found a time treefor a line
//we would be ignoring all the timetrees that we skipped over last time, because
// their time length was too small. But now that we are using a smaller time length,
// they may be applicaable for this round.
//next time trees that have not been explored yet
scratchPath.clear();
scratchPath.add(myAp.getTimeTree());
//we start calculating a lot of lines and end with a little
int maxLines = Math.max(MAX_CALCULATED_LINES_PER_ROUND - lastNumberOfViewNodesLinesCalculatedFor, MIN_CALCULATED_LINES_PER_ROUND);
int calculatedLines = 0;
while(!scratchPath.isEmpty())
{
TimeTree tt = scratchPath.remove(scratchPath.size()-1);
int timeLengthSec = tt.getTimeSec();
//if we are at the bottom level and
//if we haven't (probably) already created the line
//(incase there are two gaps exactly the same size, we will reprocess time gaps that are exactly the same
// size as last time)
// we only create lines for the bottom level because we would end up recreating them again and again
// for each timetree child.
if(tt.isBottomLevel() && timeLengthSec <= largestTimeTreeLengthForUncreatedLines)
{
// Log.d(GTG.TAG,"creating lines for child size "+childTimeLengthSec+
// " tt "+childTt);
calculatedLines += addLineForGapStart(tt, startTimeToViewLine, endTimeToViewLine) ? 1 : 0;
calculatedLines += addLineForGapEnd(tt, startTimeToViewLine, endTimeToViewLine) ? 1 : 0;
//if we calculated enough lines for this round
if(calculatedLines >= maxLines)
{
largestTimeTreeLengthForUncreatedLines = timeLengthSec-1;
return; //exit to work on some other nodes for awhile
}
}
for(int i = 0; i < TimeTree.NUM_NODES; i++)
{
TimeTree childTt = tt.getSubNode(i);
//no more tt's
if(childTt == null)
break;
//skip children out of range
if(childTt.getMaxTimeSecs() <= apStBox.minZ)
continue;
//if out of range on the higher time side then the rest will never be included
// in the stb
if(childTt.getMinTimeSecs() >= apStBox.maxZ)
break;
//insert in order of greatest time sec last (and remove from last to first)
int childLocation = Collections.binarySearch(scratchPath, childTt, TIME_TREE_TIME_SEC_COMPARATOR);
if(childLocation < 0)
{
// x = -(insertion point) - 1
// (insertion point) = -x -1
scratchPath.add(-childLocation - 1, childTt);
}
else
scratchPath.add(childLocation, childTt);
}
}
//we have crawled through all of them
largestTimeTreeLengthForUncreatedLines = Integer.MIN_VALUE;
}
private boolean addLineForGapStart(TimeTree tt,
HashMap<Integer, ViewLine> startTimeToViewLine, HashMap<Integer, ViewLine> endTimeToViewLine) {
if(tt.getMaxTimeSecs() > stBox.minZ)
{
int startTimeSec = tt.getMinTimeSecs();
//the start of the tt equals the end of the viewline
ViewLine existingViewLine = endTimeToViewLine.get(startTimeSec);
//co: even lines between neighboring points are visibile depending on the size
//of the ap dot.
// AreaPanel ap1 = tt.getPrevAp();
// AreaPanel ap2 = ap();
//
// //if were at the very end
// if(ap1 == null)
// return; //there is no line to draw
//
// if(isLineTooShortToDraw(ap1, ap2))
// {
// if(existingViewLine != null)
// {
// startTimeToViewLine.remove(existingViewLine.startTimeSec);
// endTimeToViewLine.remove(existingViewLine.endTimeSec);
// }
// return;
// }
if(existingViewLine == null)
{
int prevApEndTime = tt.getPrevApPrevTtEndTime();
//if we're at the absolute beginning there will be no prev ap
if(prevApEndTime == Integer.MIN_VALUE)
return false;
//we don't include lines that are completely off the st box
if(prevApEndTime < stBox.minZ)
return false;
existingViewLine = new ViewLine(startTimeSec, prevApEndTime);
startTimeToViewLine.put(existingViewLine.startTimeSec, existingViewLine);
endTimeToViewLine.put(existingViewLine.endTimeSec, existingViewLine);
}
existingViewLine.startApId = tt.getPrevApId();
existingViewLine.endApId = apId;
return true;
}
return false;
}
private boolean addLineForGapEnd(TimeTree tt,
HashMap<Integer, ViewLine> startTimeToViewLine, HashMap<Integer, ViewLine> endTimeToViewLine) {
//note that we must show lines that are half cut off by the stbox. Consider a line from point A at 10:00 and point B at 11:00.
// At 10:30 - 10:35 there are no points. So if we display no line then it is blank. Now suppose we zoom out so that A and B
// merge into one areapanel C. C will not have a separation in its timetrees for the gap between A and B. So we have to show
// C. This makes it inconsistent when we view C compared to when we view nothing when zoomed in where A and B become visible.
if(tt.getMinTimeSecs() < stBox.maxZ)
{
int endTimeSec = tt.getMaxTimeSecs();
//the end of the tt equasl the start of the viewline
ViewLine existingViewLine = startTimeToViewLine.get(endTimeSec);
//co: even lines between neighboring points are visibile depending on the size
//of the ap dot.
// AreaPanel ap1 = ap();
// AreaPanel ap2 = tt.getNextAp();
//
// //if were at the very end
// if(ap2 == null)
// return; //there is no line to draw
//
// if(isLineTooShortToDraw(ap1, ap2))
// {
// if(existingViewLine != null)
// {
// startTimeToViewLine.remove(existingViewLine.startTimeSec);
// endTimeToViewLine.remove(existingViewLine.endTimeSec);
// }
// return;
// }
if(existingViewLine == null)
{
int nextApStartTime = tt.getNextApNextTtStartTime();
//if we're at the absolute end there will be no next ap
if(nextApStartTime == Integer.MIN_VALUE)
return false;
//we don't include lines half way off the stbox
if(nextApStartTime > stBox.maxZ)
return false;
existingViewLine = new ViewLine(nextApStartTime, endTimeSec);
startTimeToViewLine.put(existingViewLine.startTimeSec, existingViewLine);
endTimeToViewLine.put(existingViewLine.endTimeSec, existingViewLine);
}
existingViewLine.startApId = apId;
existingViewLine.endApId = tt.getNextApId();
if(existingViewLine.startApId == Integer.MIN_VALUE || existingViewLine.endApId == Integer.MIN_VALUE)
throw new IllegalStateException("you know what you doing "+existingViewLine);
return true;
}
return false;
}
// /**
// * Returns true if the line is within 1 unit for the current ap depth either
// * vertically, horizontally or both.
// */
// private boolean isLineTooShortToDraw(AreaPanel ap1, AreaPanel ap2) {
// if(1==1) return false;
// int x1 = ap1.getX();
// int x2 = ap2.getX();
// int y1 = ap1.getY();
// int y2 = ap2.getY();
//
// if(y2 - y1 <= ap1.getWidth() && x2 - x1 <= ap1.getWidth())
// return true;
//
// return false;
// }
/**
* This calculates a really cheap estimate of distance in meters
* between the center of the aps. It is only used to choose a
* proper size of the view node, so we really don't care much.
*
* @return distance in area panel units
*/
private int estimateDist(AreaPanel ap1, AreaPanel ap2) {
int yDist = Math.abs(ap1.getCenterY() - ap2.getCenterY());
int xDist = Math.abs(ap1.getCenterX() - ap2.getCenterX());
if(xDist < (yDist >> 1) || yDist < (xDist >>1))
return xDist + yDist;
return (int) ((xDist + yDist) * 7l /10) ;
}
// meaningfully means that it decreased or moved in a way that would affect
// whether the area panel is on
// if the area panels min and max times were on Jun 22, 1991 and the range decreased
// from Jun 21 - Jun 29 to Jun 21 - Jun 28, that would NOT be meaningful.
// NOTE, As long as the resulting localStBox isn't internal, we won't be
// doing a complex time search, and this pre checking won't result
// in much speed up, so these only check internal conditions
public boolean timeDecreasedMeaningfully(AreaPanelSpaceTimeBox newLocalStBox,
AreaPanel ap) {
if((newLocalStBox.minZ <= stBox.minZ
|| newLocalStBox.minZ <= ap.getStartTimeSec())
&& (newLocalStBox.maxZ >= stBox.maxZ
|| newLocalStBox.maxZ >= ap.getEndTimeSec()) )
return false;
//PERF: we could keep track of time tree min and max internally from last match
//in the status
return true;
}
public boolean timeIncreasedMeaningfully(AreaPanelSpaceTimeBox newLocalStBox, AreaPanel ap) {
//note if the ap is the head ap and there are no points, it will exist withouth a timetree
if(ap.getTimeTreeFk() != Integer.MIN_VALUE && ( newLocalStBox.minZ < stBox.minZ && stBox.minZ > ap.getStartTimeSec() ||
newLocalStBox.maxZ > stBox.maxZ && stBox.maxZ < ap.getEndTimeSec())
)
return true;
return false;
}
public boolean outsideOfXY(AreaPanelSpaceTimeBox newLocalStBox, AreaPanel ap) {
// if we are wrapping 0 degrees longitude
if (newLocalStBox.maxX < newLocalStBox.minX) {
return (ap.getMaxX() < newLocalStBox.minX && ap.getX() > newLocalStBox.maxX)
|| ap.getMaxY() < newLocalStBox.minY
|| ap.getY() > newLocalStBox.maxY;
}
return ap.getMaxX() < newLocalStBox.minX
|| ap.getMaxY() < newLocalStBox.minY
|| ap.getX() > newLocalStBox.maxX
|| ap.getY() > newLocalStBox.maxY;
}
public void clearLineCalcs() {
largestTimeTreeLengthForUncreatedLines = Integer.MAX_VALUE;
}
}