![JAR search and dependency download from the Maven repository](/logo.png)
com.sun.electric.database.geometry.PolyQTree Maven / Gradle / Ivy
/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: PolyQTree.java
* Written by Gilda Garreton, Sun Microsystems.
*
* Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
*
* Electric(tm) 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.
*
* Electric(tm) 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 Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.database.geometry;
import com.sun.electric.technology.Layer;
import com.sun.electric.util.math.FixpTransform;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* This class represents a quad-tree to compute overlapping regions.
* @author Gilda Garreton
* @version 0.1
*/
public class PolyQTree extends GeometryHandler
{
private static int MAX_NUM_CHILDREN = 4;
private static int MAX_NUM_NODES = 10;
//private static int MAX_DEPTH = 10;
private Rectangle2D rootBox;
//--------------------------PUBLIC METHODS--------------------------
public PolyQTree(Rectangle2D root)
{
rootBox = root;
}
/**
* Print all nodes in the tree.
* Debugging purpose only!.
*/
public void print()
{
for (Object obj : layers.values())
{
PolyQNode root = (PolyQNode)obj;
if (root != null)
root.print();
}
}
/**
* Retrieves list of leaf elements in the tree for a given layer
* @param layer Layer under analysis
* @param modified True if only the original elements should not be retrieved
* @param simple True if simple elements should be retrieved
* @return list of leaf elements
*/
public Collection getObjects(Object layer, boolean modified, boolean simple)
{
new Error("This class is being decomissioned");
Set objSet = new HashSet();
PolyQNode root = (PolyQNode)layers.get(layer);
if (root != null)
{
// First collect all leaves. They might be repeated so I must collect them first
root.getLeafObjects(objSet, modified, simple);
// Checking if element must be replaced
Set toRemove = new HashSet();
Set toAdd = new HashSet();
for (Iterator it = objSet.iterator(); it.hasNext();)
{
PolyNode node = it.next();
//if (!modified || (modified && !node.isOriginal()))
{
// boolean old = (!(node.isOriginal() || !simple));
// boolean newC = !node.isOriginal() && simple;
// if (newC != old)
// System.out.println("Aqui wribg");
if (!(node.isOriginal() || !simple))
{
toRemove.add(node);
toAdd.addAll(node.getSimpleObjects(false));
}
}
}
objSet.addAll(toAdd);
objSet.removeAll(toRemove);
}
return (objSet);
}
/**
* Given a layer, insert the object obj into the qTree associated.
* @param layer Given layer to work with
*/
public void add(Layer layer, Object newObj, boolean fasterAlgorithm)
{
PolyNode obj = (PolyNode)newObj;
PolyQNode root = (PolyQNode)layers.get(layer);
if (root == null)
{
root = new PolyQNode();
layers.put(layer, root);
};
// Only if no other identical element was found, element is inserted
Rectangle2D areaBB = obj.getBounds2D();
Set removedElems = new HashSet();
if (!root.findAndRemoveObjects(rootBox, obj, areaBB, fasterAlgorithm, removedElems))
{
// Add removed elements. They overlap with new element.
for (Iterator it = removedElems.iterator(); it.hasNext(); )
{
PolyNode node = it.next();
obj.add(node);
}
// Recalculate the new bounding box because it might have extended
areaBB = obj.getBounds2D();
root.insert(rootBox, obj, areaBB, removedElems);
}
}
/**
* Merge two PolyQTree.
*/
public void addAll(GeometryHandler subMerge, FixpTransform trans)
{
PolyQTree other = (PolyQTree)subMerge;
for(Iterator it = other.layers.keySet().iterator(); it.hasNext();)
{
Layer layer = it.next();
Set set = (Set)other.getObjects(layer, false, false);
for(Iterator i = set.iterator(); i.hasNext(); )
{
PolyNode geo = (PolyNode)i.next();
PolyNode clone = new PolyNode(geo); // Only clone can be transformed.
if (trans != null) clone.transform(trans);
add(layer, clone, false);
}
}
}
//--------------------------PRIVATE METHODS--------------------------
/**
* Class to define a node in a Quad Tree of polygons.
*/
public static class PolyNode extends Area
implements Comparable, PolyNodeMerge
{
private byte original;
public PolyNode(Shape shape)
{
super(shape);
}
/**
* Compare objects based on area.
* This method doesn't guarantee (compare(x, y)==0) == (x.equals(y))
* because x.equals relies on Area.equals()
* @return Returns a negative integer, zero, or a positive integer as the
* first object has smaller than, equal to, or greater area than the second.
*/
@Override
public int compareTo(PolyNode n1)
{
return ((int)(getArea() - n1.getArea()));
}
@Override
public boolean equals(Object obj)
{
// reflexive
if (obj == this) return true;
// should consider null case
// symmetry but violates transitivity?
// It seems Map doesn't provide obj as PolyNode
if (!(obj instanceof Area))
return obj.equals(this);
Area a = (Area)obj;
return (super.equals(a));
}
@Override
public PolyBase getPolygon()
{
return (new PolyBase(getPoints(false)));
}
/**
* Not to violate that equal objects must have equal hashcodes.
*/
public int hasCode()
{
throw new UnsupportedOperationException();
}
/**
* Method to calculate longest edge.
* @return the length of the longest edge in this PolyQTree.
*/
public double getMaxLength()
{
Point2D[] points = getPoints(false);
double max = 0;
for(int i=0; i pointList = new ArrayList();
PolyBase.Point lastMoveTo = null;
while (!pi.isDone()) {
int type = pi.currentSegment(coords);
switch (type) {
case PathIterator.SEG_CLOSE:
// next available loop
if (includeInitialPoint && lastMoveTo != null)
pointList.add(lastMoveTo);
lastMoveTo = null;
break;
default:
PolyBase.Point pt = PolyBase.fromLambda(coords[0], coords[1]);
pointList.add(pt);
// Adding the point at the beginning of the loop
if (type == PathIterator.SEG_MOVETO)
lastMoveTo = pt;
}
pi.next();
}
return pointList.toArray(new PolyBase.Point[pointList.size()]);
}
public double getPerimeter()
{
double [] coords = new double[6];
List pointList = new ArrayList();
double perimeter = 0;
PathIterator pi = getPathIterator(null);
while (!pi.isDone()) {
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_CLOSE:
{
Object [] points = pointList.toArray();
for (int i = 0; i < pointList.size(); i++)
{
int j = (i + 1)% pointList.size();
perimeter += ((Point2D)points[i]).distance((Point2D)points[j]);
}
pointList.clear();
}
break;
case PathIterator.SEG_MOVETO:
default:
Point2D pt = new Point2D.Double(coords[0], coords[1]);
pointList.add(pt);
}
pi.next();
}
return(perimeter);
}
public boolean doesTouch(PathIterator opi)
{
// Adding first edges into hashMap
class PolyEdge
{
private Point2D p1, p2;
PolyEdge(Point2D a, Point2D b)
{
this.p1 = a;
this.p2 = b;
}
public int hashCode()
{
return (p1.hashCode() ^ p2.hashCode());
}
public boolean equals(Object obj)
{
if (obj == this) return (true);
if (!(obj instanceof PolyEdge)) return (false);
PolyEdge edge = (PolyEdge)obj;
return ((p1.equals(edge.p1) && p2.equals(edge.p2)) ||
(p1.equals(edge.p2) && p2.equals(edge.p1)));
}
}
HashMap edges = new HashMap();
PathIterator pi = getPathIterator(null);
double [] coords = new double[6];
List pointList = new ArrayList();
while (!pi.isDone()) {
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_CLOSE:
{
Object [] points = pointList.toArray();
for (int i = 0; i < pointList.size(); i++)
{
int j = (i + 1)% pointList.size();
PolyEdge edge = new PolyEdge((Point2D)points[i], (Point2D)points[j]);
edges.put(edge, edge);
}
pointList.clear();
}
break;
default:
Point2D pt = new Point2D.Double(coords[0], coords[1]);
pointList.add(pt);
}
pi.next();
}
// Adding the other polygon
while (!opi.isDone()) {
switch (opi.currentSegment(coords)) {
case PathIterator.SEG_CLOSE:
{
Object [] points = pointList.toArray();
for (int i = 0; i < pointList.size(); i++)
{
int j = (i + 1)% pointList.size();
Point2D p1 = (Point2D)points[i];
Point2D p2 = (Point2D)points[j];
if (p1.equals(p2))
continue;
PolyEdge edge = new PolyEdge(p1, p2);
if (edges.containsKey(edge))
return (true);
edges.put(edge, edge);
}
pointList.clear();
}
break;
default:
Point2D pt = new Point2D.Double(coords[0], coords[1]);
pointList.add(pt);
}
opi.next();
}
return (false);
}
/**
* Calculates area
* @return area associated to the node
*/
public double getArea()
{
if (isRectangular())
{
Rectangle2D bounds = getBounds2D();
return (bounds.getHeight()*bounds.getWidth());
}
// @TODO: GVG Missing part. Run more robust tests
double [] coords = new double[6];
List pointList = new ArrayList();
double area = 0;
PathIterator pi = getPathIterator(null);
while (!pi.isDone()) {
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_CLOSE:
{
Object [] points = pointList.toArray();
for (int i = 0; i < pointList.size(); i++)
{
int j = (i + 1)% pointList.size();
area += ((Point2D)points[i]).getX()*((Point2D)points[j]).getY();
area -= ((Point2D)points[j]).getX()*((Point2D)points[i]).getY();
}
pointList.clear();
}
break;
default:
Point2D pt = new Point2D.Double(coords[0], coords[1]);
pointList.add(pt);
}
pi.next();
}
area /= 2;
return(area < 0 ? -area : area);
}
/**
* Returns a printable version of this PolyNode.
* @return a printable version of this PolyNode.
*/
public String toString()
{
return ("PolyNode " + getBounds());
}
/**
* Overwriting original for Area to consider touching polygons
*/
public boolean intersects (Area a)
{
if (a.isRectangular())
{
boolean inter = intersects(a.getBounds2D());
if (inter) return (inter);
// detecting if they touch each other...
inter = doesTouch(a.getBounds2D().getPathIterator(null));
return (inter);
}
else if (isRectangular())
{
return (a.intersects(getBounds2D()));
}
// @TODO: GVG Missing part. Doesn't detect if elements are touching
// @TODO: GVG very expensive?
Area area = (Area)this.clone();
area.intersect(a);
boolean inter = !area.isEmpty();
return (inter);
}
private boolean isOriginal()
{
return ((original >> 0 & 1) == 0);
}
private void setNotOriginal()
{
original |= 1 << 0;
}
private Collection getSimpleObjects(boolean simple)
{
Set set = new HashSet();
// Possible not connected loops
double [] coords = new double[6];
List pointList = new ArrayList();
PathIterator pi = getPathIterator(null);
List polyList = new ArrayList();
boolean isSingular = isSingular();
List toDelete = new ArrayList();
while (!pi.isDone()) {
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_CLOSE:
{
Object [] points = pointList.toArray();
GeneralPath simplepath = new GeneralPath();
for (int i = 0; i < pointList.size(); i++)
{
int j = (i + 1)% pointList.size();
Line2D line = new Line2D.Double(((Point2D)points[i]), (Point2D)points[j]);
simplepath.append(line, true);
}
toDelete.clear();
//PolyNode node = new PolyNode(simplepath);
// Search possible inner loops
if (!simple && !isSingular)
{
Iterator it = polyList.iterator();
while (it.hasNext())
{
GeneralPath pn = it.next();
if (pn.contains(pointList.get(0)))
{
pn.append(simplepath.getPathIterator(null), true);
simplepath = null;
break;
}
else if (simplepath.contains(pn.getCurrentPoint()))
{
// Checking if inner loop is pn
simplepath.append(pn.getPathIterator(null), true);
toDelete.add(pn);
//break; // @TODO might not work with double loops!!
}
}
}
//set.add(node);
if (simplepath != null)
polyList.add(simplepath);
polyList.removeAll(toDelete);
pointList.clear();
}
break;
default:
Point2D pt = new Point2D.Double(coords[0], coords[1]);
pointList.add(pt);
}
pi.next();
}
for (Iterator it = polyList.iterator(); it.hasNext();)
{
GeneralPath pn = it.next();
PolyNode node = new PolyNode(pn);
set.add(node);
}
return set;
}
/**
* Sort list of objects based on area
*/
public List getSortedLoops()
{
Collection set = getSimpleObjects(true);
List list = new ArrayList(set);
Collections.sort(list);
return (list);
}
}
private static class PolyQNode
{
private Set nodes; // If Set, no need to check whether they are duplicated or not. Java will do it for you
private PolyQNode[] children;
/**
*
*/
private PolyQNode () { ; }
private int getQuadrants(double centerX, double centerY, Rectangle2D box)
{
int loc = 0;
if (box.getMinY() < centerY)
{
// either 0 or 1 quadtrees
if (box.getMinX() < centerX)
loc |= 1 << 0;
if (box.getMaxX() > centerX)
loc |= 1 << 1;
}
if (box.getMaxY() > centerY)
{
// the other quadtrees
if (box.getMinX() < centerX)
loc |= 1 << 2;
if (box.getMaxX() > centerX)
loc |= 1 << 3;
}
return loc;
}
/**
* Calculates the bounding box of a child depending on the location. Parameters are passed to avoid
* extra calculation
* @param x Parent x value
* @param y Parent y value
* @param w Child width (1/4 of parent if qtree)
* @param h Child height (1/2 of parent if qtree)
* @param centerX Parent center x value
* @param centerY Parent center y value
* @param loc Location in qtree
*/
private Rectangle2D getBox(double x, double y, double w, double h, double centerX, double centerY, int loc)
{
if ((loc >> 0 & 1) == 1)
{
x = centerX;
}
if ((loc >> 1 & 1) == 1)
{
y = centerY;
}
return (new Rectangle2D.Double(x, y, w, h));
}
/**
* Collects recursive leaf elements in a list. Uses set to avoid
* duplicate elements (qtree could sort same element in all quadrants
* @param modified True if no original elements should be considered
* @param simple True if simple elements should be retrieved
*/
protected void getLeafObjects(Set set, boolean modified, boolean simple)
{
if (nodes != null)
{
// Not sure how efficient this is
for (Iterator it = nodes.iterator(); it.hasNext();)
{
PolyNode node = it.next();
if (!modified || (modified && !node.isOriginal()))
{
//if (node.isOriginal() || !simple)
set.add(node);
//else
//set.addAll(node.getSimpleObjects(false));
}
}
}
if (children == null) return;
for (int i = 0; i < PolyQTree.MAX_NUM_CHILDREN; i++)
{
if (children[i] != null) children[i].getLeafObjects(set, modified, simple);
}
}
/**
* print function for debugging purposes
*/
protected void print()
{
if (nodes == null) return;
for (Iterator it = nodes.iterator(); it.hasNext();)
System.out.println("Area " + it.next());
if (children == null) return;
for (int i = 0; i < PolyQTree.MAX_NUM_CHILDREN; i++)
{
if (children[i] != null) children[i].print();
}
}
/**
* To compact nodes if child elements have been removed
* @return true if node can be removed
*/
private boolean compact()
{
//@TODO GVG Compact tree
if (children != null)
{
for (int i = 0; i < PolyQTree.MAX_NUM_CHILDREN; i++)
if (children[i] != null)
{
//System.out.println("To implement") ;
return (false);
}
}
return (nodes == null || nodes.isEmpty());
}
// /**
// * Original Rectangle2D:intersects doesn't detect when two elements are touching
// */
// private static boolean intersects(Rectangle2D a, Rectangle2D b)
// {
// double x = b.getX();
// double y = b.getY();
// double w = b.getWidth();
// double h = b.getHeight();
//
// if (a.isEmpty() || w <= 0 || h <= 0) {
// return false;
// }
// double x0 = a.getX();
// double y0 = a.getY();
// return ((x + w) >= x0 &&
// (y + h) >= y0 &&
// x <= (x0 + a.getWidth()) &&
// y <= (y0 + a.getHeight()));
// }
/**
* Removes from tree all objects overlapping with obj. Returns the overlapping region.
*/
protected boolean findAndRemoveObjects(Rectangle2D box, PolyNode obj, Rectangle2D areaBB,
boolean fasterAlgorithm, Set removedElems)
{
double centerX = box.getCenterX();
double centerY = box.getCenterY();
// Node has been split
if (children != null)
{
int loc = getQuadrants(centerX, centerY, areaBB);
double w = box.getWidth()/2;
double h = box.getHeight()/2;
double x = box.getX();
double y = box.getY();
for (int i = 0; i < PolyQTree.MAX_NUM_CHILDREN; i++)
{
if (((loc >> i) & 1) == 1)
{
Rectangle2D bb = getBox(x, y, w, h, centerX, centerY, i);
// if identical element was found, no need of re-insertion
// No need of reviewing other quadrants?
if (children[i] == null) continue;
if (children[i].findAndRemoveObjects(bb, obj, areaBB, fasterAlgorithm, removedElems))
return (true);
if (children[i].compact())
children[i] = null;
}
}
}
else if (nodes != null)
{
Set deleteSet = new HashSet();
for (Iterator it = nodes.iterator(); it.hasNext();)
{
PolyNode node = it.next();
if (node.equals((Object)obj))
{
if (removedElems.size() != 0)
System.out.println("I will have problems here!!");
return (true); // I will have problems here!!!
}
// @TODO this should be reviewed!!
if (!fasterAlgorithm || (fasterAlgorithm && node.intersects(obj)))
{
//obj.add(node); // It might removed immeadiately in other quadrants
removedElems.add(node);
obj.setNotOriginal();
deleteSet.add(node);
}
}
nodes.removeAll(deleteSet);
if (nodes.size() == 0)
{
// not sure yet
nodes.clear();
nodes = null;
}
}
// No identical element found
return (false);
}
/**
* To make sure new element is inserted in all childres
* @param box Bounding box of current node
* @param centerX To avoid calculation inside function from object box
* @param centerY To avoid calculation inside function from object box
* @param obj Object to insert
* @param areaBB Bounding box of the object to insert
* @return True if element was inserted
*/
protected boolean insertInAllChildren(Rectangle2D box, double centerX, double centerY, PolyNode obj,
Rectangle2D areaBB, Set removedElems)
{
int loc = getQuadrants(centerX, centerY, areaBB);
boolean inserted = false;
double w = box.getWidth()/2;
double h = box.getHeight()/2;
double x = box.getX();
double y = box.getY();
for (int i = 0; i < PolyQTree.MAX_NUM_CHILDREN; i++)
{
if (((loc >> i) & 1) == 1)
{
Rectangle2D bb = getBox(x, y, w, h, centerX, centerY, i);
if (children[i] == null) children[i] = new PolyQNode();
boolean done = children[i].insert(bb, obj, areaBB, removedElems);
inserted = (inserted) ? inserted : done;
}
}
return (inserted);
}
/**
* Method.
* @param box Bounding box of the current PolyQNode
* @param obj Object to insert
* @param areaBB Bounding box of object to insert
* @param removedElems list of old nodes that might need to be replaced
* @return if node was really inserted
*/
protected boolean insert(Rectangle2D box, PolyNode obj, Rectangle2D areaBB, Set removedElems)
{
if (!box.intersects(areaBB))
{
// new element is outside of bounding box. Might need flag to avoid
// double checking if obj is coming from findAndRemove
return (false);
}
double centerX = box.getCenterX();
double centerY = box.getCenterY();
// Node has been split
if (children != null)
{
return (insertInAllChildren(box, centerX, centerY, obj, areaBB, removedElems));
}
if (nodes == null)
{
nodes = new HashSet();
}
boolean inserted = false;
if (nodes.size() < PolyQTree.MAX_NUM_NODES)
{
inserted = nodes.add(obj);
// still might have references to previous nodes that were merged.
if(removedElems != null)
nodes.removeAll(removedElems);
// nodes.add(obj.clone());
}
else
{
// subdivides into PolyQTree.MAX_NUM_CHILDREN. Might work only for 2^n
children = new PolyQNode[PolyQTree.MAX_NUM_CHILDREN];
double w = box.getWidth()/2;
double h = box.getHeight()/2;
double x = box.getX();
double y = box.getY();
// Redistributing existing elements in children
for (int i = 0; i < PolyQTree.MAX_NUM_CHILDREN; i++)
{
children[i] = new PolyQNode();
Rectangle2D bb = getBox(x, y, w, h, centerX, centerY, i);
for (Iterator it = nodes.iterator(); it.hasNext();)
{
PolyNode node = it.next();
children[i].insert(bb, node, node.getBounds2D(), removedElems);
}
}
nodes.clear(); // not sure about this clear yet
nodes = null;
inserted = insertInAllChildren(box, centerX, centerY, obj, areaBB, null);
}
return (inserted);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy