All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.electric.database.geometry.PolyQTree Maven / Gradle / Ivy

There is a newer version: 9.02-e
Show newest version
/* -*- 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