Package aimax.osm.data.impl

Source Code of aimax.osm.data.impl.KDTree$VisibilityTest

package aimax.osm.data.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import aimax.osm.data.BoundingBox;
import aimax.osm.data.EntityVisitor;
import aimax.osm.data.entities.MapEntity;


/**
* Implementation of a kd-tree (node) for map entities. Entities are
* divided by their two-dimensional geographical coordinates and within each
* kd-tree node ordered by ascending minimal visible scale. Note, that
* inner nodes can contain entities, e.g. ways, whose nodes do
* not completely fit into the bounding box of one of the child nodes.
* @author Ruediger Lunde
*/
public class KDTree {
  private BoundingBox bb;
  private KDTree[] children; // null or array of length 2
  private int depth;
  private int maxEntities;
  private int maxDepth;
  private ArrayList<DefaultMapEntity> entities;
  private boolean splitAtLat;
  private float splitValue;
  private boolean isSorted;
 
  /**
   * Constructs the root of the tree.
   * @param bb The bounding box. Only entities within the box can be added.
   * @param maxEntities Defines, when a leaf node should be split.
   * @param maxDepth Limits the depth of the tree.
   */
  public KDTree(BoundingBox bb, int maxEntities, int maxDepth) {
    this.bb = bb;
    depth = 0;
    this.maxEntities = maxEntities;
    this.maxDepth = maxDepth;
    entities = new ArrayList<DefaultMapEntity>();
  }
 
  /** Constructor for inner and leaf nodes. */
  private KDTree(BoundingBox bb, int maxEntities, int maxDepth, int depth) {
    this(bb, maxEntities, maxDepth);
    this.depth = depth;
  }
 
  public BoundingBox getBoundingBox() {
    return bb;
  }
 
  /** Returns the depth of the tree (longest path length from root to leaf). */
  public int depth() {
    return children == null ? 1 : 1+Math.max(children[0].depth(), children[1].depth());
  }
 
  /** Must be called after classification of entities has been changed. */
  public void setUnsorted() {
    isSorted = false;
    if (children != null) {
      children[0].setUnsorted();
      children[1].setUnsorted();
    }
  }
 
  /**
   * Adds an entity at the right position in the tree and extends
   * the tree if necessary. It is assumed that the entity contains
   * view information.
   */
  public void insertEntity(DefaultMapEntity entity) {
    if (children == null) {
      entities.add(entity);
      isSorted = false;
      if (entities.size() > maxEntities && depth < maxDepth) {
        computeSplitValues();
        BoundingBox c1bb;
        BoundingBox c2bb;
        if (splitAtLat) {
          c1bb = new BoundingBox
          (bb.getLatMin(), bb.getLonMin(), splitValue, bb.getLonMax());
          c2bb = new BoundingBox
          (splitValue, bb.getLonMin(), bb.getLatMax(), bb.getLonMax());
        } else {
          c1bb = new BoundingBox
          (bb.getLatMin(), bb.getLonMin(), bb.getLatMax(), splitValue);
          c2bb = new BoundingBox
          (bb.getLatMin(), splitValue, bb.getLatMax(), bb.getLonMax());
        }
        children = new KDTree[2];
        children[0] = new KDTree(c1bb, maxEntities, maxDepth, depth+1);
        children[1] = new KDTree(c2bb, maxEntities, maxDepth, depth+1);
        List<DefaultMapEntity> tmp = entities;
        entities = new ArrayList<DefaultMapEntity>();
        for (DefaultMapEntity ne : tmp)
          insertEntity(ne);
      }
    } else {
      int cr = (splitAtLat
          ? entity.compareLatitude(splitValue)
          : entity.compareLongitude(splitValue));
      if (cr < 0)
        children[0].insertEntity(entity);
      else if (cr > 0)
        children[1].insertEntity(entity);
      else {
        entities.add(entity);
        isSorted = false;
      }
    }
  }
 
  /**
   * Splits the bounding box, so that the new boxes have equal size
   * and look square-like as much as possible.
   */
  private void computeSplitValues() {
    float latMid = (bb.getLatMin()+bb.getLatMax()) / 2f;
    double width = (bb.getLonMax()-bb.getLonMin()) * Math.cos(latMid * Math.PI / 180.0);
    double height = bb.getLatMax()-bb.getLatMin();
    if (height > width) {
      splitAtLat = true;
      splitValue = latMid;
    } else {
      splitAtLat = false;
      splitValue = (bb.getLonMin()+bb.getLonMax()) / 2;
    }
  }
 
  /** Returns for each split bar the end points (lat1, lon1, lat2, lon2). */
  public List<double[]> getSplitCoords() {
    List<double[]> result;
    double[] coords;
    if (splitAtLat) {
      double lonDiff = (bb.getLonMax()-bb.getLonMin()) / 20;
      coords = new double[] {
        splitValue, bb.getLonMin()+lonDiff,
        splitValue, bb.getLonMax()-lonDiff };
    } else {
      double latDiff = (bb.getLatMax()-bb.getLatMin()) / 20;
      coords = new double[] {
          bb.getLatMin()+latDiff, splitValue,
          bb.getLatMax()-latDiff, splitValue };
    }
    if (children == null) {
      result = new ArrayList<double[]>();
    } else {
      result = children[0].getSplitCoords();
      result.addAll(children[1].getSplitCoords());
    }
    result.add(coords);
    return result;
  }
 
  /**
   * Enables to iterate across all contained entities within a given region
   * in an intelligent manner. Only tree nodes, which have a chance to
   * meet the location requirement are visited.
   */
  public void visitEntities(EntityVisitor visitor, BoundingBox vbox, float scale) {
    if (!entities.isEmpty()) {
      if (!isSorted) {
        Collections.sort(entities, new EntityComparator());
        isSorted = true;
      }
      VisibilityTest vtest = new VisibilityTest(bb, vbox);
      for (DefaultMapEntity entity : entities) {
        if (entity.getViewInfo().getMinVisibleScale() > scale)
          break;
        if (vtest.isVisible(entity))
          entity.accept(visitor);
      }
    }
    if (children != null) {
      float vMin = (splitAtLat ? vbox.getLatMin() : vbox.getLonMin());
      float vMax = (splitAtLat ? vbox.getLatMax() : vbox.getLonMax());
      if (vMin <= splitValue)
        children[0].visitEntities(visitor, vbox, scale);
      if (vMax >= splitValue)
        children[1].visitEntities(visitor, vbox, scale);
    }
  }

 
  /////////////////////////////////////////////////////////////////
  // some inner classes
 
  /**
   * Compares entities with respect to their minimal visible scale.
   * Entities which are already visible in small scales are preferred.
   */
  private static class EntityComparator implements Comparator<MapEntity> {
    @Override
    public int compare(MapEntity e1, MapEntity e2) {
      float vs1 = e1.getViewInfo().getMinVisibleScale();
      float vs2 = e2.getViewInfo().getMinVisibleScale();
      if (vs1 < vs2)
        return -1;
      else if (vs1 > vs2)
        return 1;
      else
        return 0;
    }
  }
 
  /**
   * If a kd-tree node's bounding box is not completely visible,
   * this class helps to check which entities are visible and which not.
   * @author Ruediger Lunde
   *
   */
  private static class VisibilityTest {
    private boolean isTrue = true;
    private float testLatMin = Float.NaN;
    private float testLonMin = Float.NaN;
    private float testLatMax = Float.NaN;
    private float testLonMax = Float.NaN;
   
    VisibilityTest(BoundingBox treeBox, BoundingBox visibleBox) {
      if (treeBox.getLatMin() < visibleBox.getLatMin()) {
        testLatMin = visibleBox.getLatMin();
        isTrue = false;
      }
      if (treeBox.getLonMin() < visibleBox.getLonMin()) {
        testLonMin = visibleBox.getLonMin();
        isTrue = false;
      }
      if (treeBox.getLatMax() > visibleBox.getLatMax()) {
        testLatMax = visibleBox.getLatMax();
        isTrue = false;
      }
      if (treeBox.getLonMax() > visibleBox.getLonMax()) {
        testLonMax = visibleBox.getLonMax();
        isTrue = false;
      }
    }
   
    boolean isVisible(DefaultMapEntity entity) {
      if (isTrue)
        return true;
      if (testLatMin != Float.NaN && entity.compareLatitude(testLatMin) < 0)
        return false; // entity below visible area
      if (testLonMin != Float.NaN && entity.compareLongitude(testLonMin) < 0)
        return false; // entity to the left of visible area
      if (testLatMax != Float.NaN && entity.compareLatitude(testLatMax) > 0)
        return false; // entity above visible area
      if (testLonMax != Float.NaN && entity.compareLongitude(testLonMax) > 0)
        return false; // entity to the right of visible area
      return true;
    }
  }
}
TOP

Related Classes of aimax.osm.data.impl.KDTree$VisibilityTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.