/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; 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.vividsolutions.jump.qa.diff; import com.vividsolutions.jts.geom.*; import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier; /** * Matches geometries based on whether each Geometry is contained in the * other's buffer. This is equivalent to each geometry being entirely * within the distance tolerance of the other. */ public class BufferGeometryMatcher implements DiffGeometryMatcher { /** * Computes whether two geometries match under * this similarity test. * This is not the most efficient way of * executing this predicate for multiple geometries. * * @param g1 a Geometry * @param g2 a Geometry * @return true if the geometries match under this comparison operation */ public static boolean isMatch(Geometry g1, Geometry g2, double tolerance) { BufferGeometryMatcher matcher = new BufferGeometryMatcher(tolerance); matcher.setQueryGeometry(g1); return matcher.isMatch(g2); } // the percentage of the buffer distance to use as the error tolerance for perturbation public static final double ERROR_TOLERANCE = .1; /** * if true the buffers of the boundary will be matched as well as the buffers of the * geometries themselves. Matching the boundaries as well is * a more accurate matching algorithm. */ private static final boolean checkBoundary = false; public static double maxOrthogonalDistance(Envelope env1, Envelope env2) { double deltaMinX = Math.abs(env1.getMinX() - env2.getMinX()); double maxDist = deltaMinX; double deltaMaxX = Math.abs(env1.getMaxX() - env2.getMaxX()); if (deltaMaxX > maxDist) maxDist = deltaMaxX; double deltaMinY = Math.abs(env1.getMinY() - env2.getMinY()); if (deltaMinY > maxDist) maxDist = deltaMinY; double deltaMaxY = Math.abs(env1.getMaxY() - env2.getMaxY()); if (deltaMaxY > maxDist) maxDist = deltaMaxY; return maxDist; } private double tolerance; private Geometry queryGeom; private Geometry queryBuffer; private Geometry queryBoundary = null; private Geometry queryBoundaryBuffer = null; public BufferGeometryMatcher(double tolerance) { this.tolerance = tolerance; } public void setQueryGeometry(Geometry geom) { queryGeom = geom; queryBuffer = checkedBuffer(geom); if (checkBoundary) { if (queryGeom.getDimension() == 2) { queryBoundary = queryGeom.getBoundary(); queryBoundaryBuffer = getBoundaryBuffer(queryGeom); } } } public Geometry getQueryGeometry() { return queryBuffer; } public boolean isMatch(Geometry geom) { if (geom.getClass() != queryGeom.getClass()) return false; if (! isEnvelopeMatch(geom)) return false; boolean buffersMatch = isBufferMatch(geom); if (! buffersMatch) return false; if (! checkBoundary) return true; else { // for non-area geometries this is all we need to check if (queryGeom.getDimension() < 2) return true; // for area geometries, check that the linework matches as well return isBoundaryBufferMatch(geom); } } private boolean isBufferMatch(Geometry geom) { Geometry buf = checkedBuffer(geom); boolean queryContains = queryBuffer.contains(geom); boolean queryIsContained = buf.contains(queryGeom); return queryContains && queryIsContained; } private boolean isBoundaryBufferMatch(Geometry geom) { Geometry boundary = geom.getBoundary(); Geometry bndBuf = getBoundaryBuffer(geom); boolean queryContains = queryBoundaryBuffer.contains(boundary); boolean queryIsContained = bndBuf.contains(queryBoundary); return queryContains && queryIsContained; } private Geometry checkedBuffer(Geometry geom) { Geometry buf = null; try { buf = geom.buffer(tolerance); //buf = bufferDP.buffer(geom); } catch (RuntimeException ex) { // hack to get around buffer robustness problems System.out.println("Buffer error!"); System.out.println(geom); buf = geom; } return buf; } private Geometry getBoundaryBuffer(Geometry geom) { double scaleFactor = 1 / (tolerance * ERROR_TOLERANCE); Geometry boundary = DouglasPeuckerSimplifier.simplify(geom.getBoundary(), scaleFactor); Geometry buf = checkedBuffer(boundary); if (buf.isEmpty()) { System.out.println("Empty boundary buffer found"); System.out.println(geom); System.out.println(boundary); } return buf; } private boolean isEnvelopeMatch(Geometry geom) { // first check envelopes - if they are too far apart the geometries do not match double envDist = maxOrthogonalDistance(queryGeom.getEnvelopeInternal(), geom.getEnvelopeInternal()); return envDist <= tolerance; } }