/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.noding.snapround; import com.revolsys.geometry.algorithm.LineIntersector; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.impl.BoundingBoxDoubleXY; import com.revolsys.geometry.model.impl.PointDoubleXY; import com.revolsys.geometry.noding.NodedSegmentString; import com.revolsys.geometry.util.Assert; /** * Implements a "hot pixel" as used in the Snap Rounding algorithm. * A hot pixel contains the interior of the tolerance square and * the boundary * <b>minus</b> the top and right segments. * <p> * The hot pixel operations are all computed in the integer domain * to avoid rounding problems. * * @version 1.7 */ public class HotPixel extends PointDoubleXY { /** * */ private static final long serialVersionUID = 1L; private static final double SAFE_ENV_EXPANSION_FACTOR = 0.75; private final LineIntersector li; private final double maxx; private final double maxy; private final double minx; private final double miny; private final Point originalPt; private BoundingBox safeEnv = null; private final double scaleFactor; /** * Creates a new hot pixel, using a given scale factor. * The scale factor must be strictly positive (non-zero). * * @param point the coordinate at the centre of the pixel * @param scaleFactor the scaleFactor determining the pixel size. Must be > 0 * @param li the intersector to use for testing intersection with line segments * */ public HotPixel(final Point point, final double scaleFactor, final LineIntersector li) { this.originalPt = point; this.x = point.getX(); this.y = point.getY(); this.scaleFactor = scaleFactor; this.li = li; // tolerance = 0.5; if (scaleFactor <= 0) { throw new IllegalArgumentException("Scale factor must be > 0"); } if (scaleFactor == 1.0) { } else { this.x = scale(point.getX()); this.y = scale(point.getY()); } this.minx = this.x - 0.5; this.maxx = this.x + 0.5; this.miny = this.y - 0.5; this.maxy = this.y + 0.5; } /** * Adds a new node (equal to the snap pt) to the specified segment * if the segment passes through the hot pixel * * @param segStr * @param segIndex * @return true if a node was added to the segment */ public boolean addSnappedNode(final NodedSegmentString segStr, final int segIndex) { double x1 = segStr.getX(segIndex); double y1 = segStr.getY(segIndex); double x2 = segStr.getX(segIndex + 1); double y2 = segStr.getY(segIndex + 1); if (this.scaleFactor != 1.0) { x1 = scale(x1); y1 = scale(y1); x2 = scale(x2); y2 = scale(y2); } if (intersectsScaled(x1, y1, x2, y2)) { segStr.addIntersection(this, segIndex); return true; } else { return false; } } /** * Gets the coordinate this hot pixel is based at. * * @return the coordinate of the pixel */ public Point getCoordinate() { return this.originalPt; } /** * Returns a "safe" envelope that is guaranteed to contain the hot pixel. * The envelope returned will be larger than the exact envelope of the * pixel. * * @return an envelope which contains the hot pixel */ public BoundingBox getSafeEnvelope() { if (this.safeEnv == null) { final double safeTolerance = SAFE_ENV_EXPANSION_FACTOR / this.scaleFactor; this.safeEnv = new BoundingBoxDoubleXY(this.originalPt.getX() - safeTolerance, this.originalPt.getY() - safeTolerance, this.originalPt.getX() + safeTolerance, this.originalPt.getY() + safeTolerance); } return this.safeEnv; } private boolean intersectsScaled(final double x1, final double y1, final double x2, final double y2) { final double segMinx = Math.min(x1, x2); final double segMaxx = Math.max(x1, x2); final double segMiny = Math.min(y1, y2); final double segMaxy = Math.max(y1, y2); final boolean isOutsidePixelEnv = this.maxx < segMinx || this.minx > segMaxx || this.maxy < segMiny || this.miny > segMaxy; if (isOutsidePixelEnv) { return false; } final boolean intersects = intersectsToleranceSquare(x1, y1, x2, y2); Assert.isTrue(!(isOutsidePixelEnv && intersects), "Found bad envelope test"); return intersects; } /** * Tests whether the segment p0-p1 intersects the hot pixel tolerance square. * Because the tolerance square point set is partially open (along the * top and right) the test needs to be more sophisticated than * simply checking for any intersection. * However, it can take advantage of the fact that the hot pixel edges * do not lie on the coordinate grid. * It is sufficient to check if any of the following occur: * <ul> * <li>a proper intersection between the segment and any hot pixel edge * <li>an intersection between the segment and <b>both</b> the left and bottom hot pixel edges * (which detects the case where the segment intersects the bottom left hot pixel corner) * <li>an intersection between a segment endpoint and the hot pixel coordinate * </ul> * * @param p0 * @param p1 * @return */ private boolean intersectsToleranceSquare(final double x1, final double y1, final double x2, final double y2) { boolean intersectsLeft = false; boolean intersectsBottom = false; this.li.computeIntersection(x1, y1, x2, y2, this.maxx, this.maxy, this.minx, this.maxy); if (this.li.isProper()) { return true; } this.li.computeIntersection(x1, y1, x2, y2, this.minx, this.maxy, this.minx, this.miny); if (this.li.isProper()) { return true; } if (this.li.hasIntersection()) { intersectsLeft = true; } this.li.computeIntersection(x1, y1, x2, y2, this.minx, this.miny, this.maxx, this.miny); if (this.li.isProper()) { return true; } if (this.li.hasIntersection()) { intersectsBottom = true; } this.li.computeIntersection(x1, y1, x2, y2, this.maxx, this.miny, this.maxx, this.maxy); if (this.li.isProper()) { return true; } if (intersectsLeft && intersectsBottom) { return true; } if (equals(x1, y1)) { return true; } if (equals(x2, y2)) { return true; } return false; } private double scale(final double val) { return Math.round(val * this.scaleFactor); } }