From 0806aecbe2e32d4756eb626f7847cfcdff8ed3c7 Mon Sep 17 00:00:00 2001 From: Max Schenkelberg Date: Tue, 2 Oct 2012 17:18:41 -0500 Subject: [PATCH] Issue #1170 Fixed slow down caused by differencing geometries too much. Switched to reconstructing polygon with holes instead of differencing to readd holes. Fixed world wrap checker to ensure points are between a 360 normalized range based on inverse central meridian. Change-Id: Icfffddeeaa9feff8d9b996ce0021acb93d216bab Former-commit-id: 5a7ed945e295de4bce77e9c42f5284e3796b6a02 [formerly 9d5a7cc39c701cf4b6bab885972431e50e19f3de] [formerly 5a7ed945e295de4bce77e9c42f5284e3796b6a02 [formerly 9d5a7cc39c701cf4b6bab885972431e50e19f3de] [formerly 291871bbadbda625002511f503f30eec522c5c73 [formerly 7ca7360f261e199c813f3a48d7d0faabada61dba]]] Former-commit-id: 291871bbadbda625002511f503f30eec522c5c73 Former-commit-id: b260613a4c7e98c958b788d5b660649625acdca7 [formerly b300507bbc63d987b0e4bb4827a5957eacb7312d] Former-commit-id: 87578544b3cde47cabe92c265879a130aff0c09a --- .../raytheon/viz/core/gl/AbstractGLMesh.java | 6 +- .../geospatial/util/WorldWrapChecker.java | 43 +++-- .../geospatial/util/WorldWrapCorrector.java | 167 ++++++++++++------ 3 files changed, 141 insertions(+), 75 deletions(-) diff --git a/cave/com.raytheon.viz.core.gl/src/com/raytheon/viz/core/gl/AbstractGLMesh.java b/cave/com.raytheon.viz.core.gl/src/com/raytheon/viz/core/gl/AbstractGLMesh.java index 7b245cb6a1..0168403fe3 100644 --- a/cave/com.raytheon.viz.core.gl/src/com/raytheon/viz/core/gl/AbstractGLMesh.java +++ b/cave/com.raytheon.viz.core.gl/src/com/raytheon/viz/core/gl/AbstractGLMesh.java @@ -356,16 +356,16 @@ public abstract class AbstractGLMesh implements IMesh { // Get various x distances to use as weights in interpolating double abDist = 360 - Math.abs(ax - bx); double acDist = 360 - Math.abs(ax - cx); - double amDist = 360 + ax - wwc.getInverseCentralMeridian(); + double amDist = ax - wwc.getLowInverseCentralMeridian(); if (amDist > 360) { amDist = amDist - 360; } // x location to use for midpoints on the triangle side, should be on // same side of central meridian as a - double tx = wwc.getInverseCentralMeridian() - 360 + 0.00001; + double tx = wwc.getLowInverseCentralMeridian() + 0.00001; // x location to use for midpoints on the quad side, should be on // same side of central meridian as b and c - double qx = wwc.getInverseCentralMeridian() - 0.00001; + double qx = wwc.getHighInverseCentralMeridian() - 0.00001; // If a is closer to the central meridian on the other side then switch // amDist, tx, and qx if (amDist > 180) { diff --git a/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/util/WorldWrapChecker.java b/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/util/WorldWrapChecker.java index 203fe09a48..a0b1fe4d55 100644 --- a/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/util/WorldWrapChecker.java +++ b/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/util/WorldWrapChecker.java @@ -51,9 +51,9 @@ import com.raytheon.uf.common.status.UFStatus.Priority; public class WorldWrapChecker { - private double inverseCentralMeridian = Double.NaN; + private double lowInverseCentralMeridian = Double.NaN; - private boolean low = false; + private double highInverseCentralMeridian = Double.NaN; private boolean checkForWrapping = false; @@ -67,16 +67,14 @@ public class WorldWrapChecker { AbstractProvider.CENTRAL_MERIDIAN.getName().getCode()) .doubleValue(); } - inverseCentralMeridian = centralMeridian + 180.0; - if (inverseCentralMeridian > 180.0) { - inverseCentralMeridian -= 360.0; - low = true; - } - double l1 = inverseCentralMeridian - .1; - double l2 = inverseCentralMeridian - .2; - double r1 = inverseCentralMeridian - 359.9; - double r2 = inverseCentralMeridian - 359.8; + highInverseCentralMeridian = centralMeridian + 180.0; + lowInverseCentralMeridian = centralMeridian - 180.0; + + double l1 = highInverseCentralMeridian - .1; + double l2 = highInverseCentralMeridian - .2; + double r1 = highInverseCentralMeridian - 359.9; + double r2 = highInverseCentralMeridian - 359.8; try { MathTransform latLonToGrid = new DefaultMathTransformFactory() @@ -121,15 +119,26 @@ public class WorldWrapChecker { return Math.abs(aLon - bLon) > 180.0; } - public double getInverseCentralMeridian() { - return inverseCentralMeridian; + /** + * @return the lowInverseCentralMeridian + */ + public double getLowInverseCentralMeridian() { + return lowInverseCentralMeridian; + } + + /** + * @return the highInverseCentralMeridian + */ + public double getHighInverseCentralMeridian() { + return highInverseCentralMeridian; } public double toProjectionRange(double aLon) { - if (low && aLon < inverseCentralMeridian) { - aLon += 360; - } else if (!low && aLon > inverseCentralMeridian) { - aLon -= 360; + while (aLon < lowInverseCentralMeridian) { + aLon += 360.0; + } + while (aLon > highInverseCentralMeridian) { + aLon -= 360.0; } return aLon; } diff --git a/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/util/WorldWrapCorrector.java b/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/util/WorldWrapCorrector.java index 9771d4c39b..866b9f10ef 100644 --- a/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/util/WorldWrapCorrector.java +++ b/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/util/WorldWrapCorrector.java @@ -20,6 +20,8 @@ package com.raytheon.uf.common.geospatial.util; import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import org.geotools.coverage.grid.GeneralGridGeometry; @@ -29,7 +31,10 @@ import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; +import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.Polygon; +import com.vividsolutions.jts.geom.prep.PreparedGeometry; +import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory; /** * This class uses the WorldWrapChecker to correct wrapping of geometries that @@ -76,7 +81,7 @@ public class WorldWrapCorrector { if (checker.needsChecking() == false) { geoms.add(geom); } else { - wrapCorrect(geom, geoms, checker.getInverseCentralMeridian()); + wrapCorrect(geom, geoms); } return geom.getFactory().createGeometryCollection( geoms.toArray(new Geometry[geoms.size()])); @@ -90,11 +95,10 @@ public class WorldWrapCorrector { * @param g * @param geomList */ - private void wrapCorrect(Geometry g, List geomList, - double inverseCentralMeridian) { + private void wrapCorrect(Geometry g, List geomList) { if (g instanceof GeometryCollection) { for (int n = 0; n < g.getNumGeometries(); ++n) { - wrapCorrect(g.getGeometryN(n), geomList, inverseCentralMeridian); + wrapCorrect(g.getGeometryN(n), geomList); } } else if (g.isEmpty() == false) { // Algorithm: @@ -108,71 +112,110 @@ public class WorldWrapCorrector { // we split it up into sections by intersecting with a 360 deg // inverse central meridian. We then normalize the points for each // section back to -180 to 180 - if (checker.needsChecking()) { - List geoms = new ArrayList(); - if (g instanceof Polygon) { - GeometryFactory gf = g.getFactory(); - Polygon p = (Polygon) g; - LineString extRing = p.getExteriorRing(); - Polygon extPolygon = gf - .createPolygon(gf.createLinearRing(extRing - .getCoordinates()), null); - double[] offsets = flattenGeometry(extPolygon); - List extRings = new ArrayList(); - correct(extRings, extPolygon, inverseCentralMeridian, - offsets[0], offsets[1]); - List intRings = new ArrayList( - p.getNumInteriorRing()); - for (int n = 0; n < p.getNumInteriorRing(); ++n) { - Polygon intRing = gf.createPolygon(gf - .createLinearRing(p.getInteriorRingN(n) - .getCoordinates()), null); - offsets = flattenGeometry(intRing); - correct(intRings, intRing, inverseCentralMeridian, - offsets[0], offsets[1]); - } - for (Geometry ext : extRings) { - for (int n1 = 0; n1 < ext.getNumGeometries(); ++n1) { - Geometry geom = ext.getGeometryN(n1); - for (Geometry intRing : intRings) { - for (int n2 = 0; n2 < intRing - .getNumGeometries(); ++n2) { - geom = geom.difference(intRing - .getGeometryN(n2)); - } - } - geoms.add(geom); + List geoms = new ArrayList(); + if (g instanceof Polygon) { + GeometryFactory gf = g.getFactory(); + Polygon p = (Polygon) g; + LineString extRing = p.getExteriorRing(); + Polygon extPolygon = gf.createPolygon( + gf.createLinearRing(extRing.getCoordinates()), null); + // World wrap correct exterior ring and extract polygons + double[] offsets = flattenGeometry(extPolygon); + List extRings = new ArrayList(); + correct(extRings, extPolygon, offsets); + List polygons = new ArrayList(); + for (Geometry geom : extRings) { + extractPolygons(polygons, geom); + } + + // World wrap correct each interior ring + List intRings = new ArrayList( + p.getNumInteriorRing()); + for (int n = 0; n < p.getNumInteriorRing(); ++n) { + Polygon intRing = gf.createPolygon(gf.createLinearRing(p + .getInteriorRingN(n).getCoordinates()), null); + offsets = flattenGeometry(intRing); + correct(intRings, intRing, offsets); + } + + // Extract polygons and "preprare" them for intersections + List interiorPolygons = new LinkedList(); + for (Geometry geom : intRings) { + extractPolygons(interiorPolygons, geom); + } + List preparedInteriorPolygons = new LinkedList(); + for (Polygon intPoly : interiorPolygons) { + preparedInteriorPolygons.add(PreparedGeometryFactory + .prepare(intPoly)); + } + + // Final polygon list (may create multipolygon out of) + List finalPolys = new ArrayList( + polygons.size()); + for (Polygon polygon : polygons) { + // For each polygon, check if it intersects any interior + // polygons. If so, add them to interior ring list so we + // can reconstruct with them in place + List interiorRings = new ArrayList(); + Iterator preparedIntPolys = preparedInteriorPolygons + .iterator(); + while (preparedIntPolys.hasNext()) { + PreparedGeometry prepIntPoly = preparedIntPolys.next(); + boolean intersects = prepIntPoly.intersects(polygon); + if (intersects) { + preparedIntPolys.remove(); + interiorRings.add(gf + .createLinearRing(((Polygon) prepIntPoly + .getGeometry()).getExteriorRing() + .getCoordinates())); } } + + if (interiorRings.size() > 0) { + // add holes to polygon + polygon = gf.createPolygon(gf.createLinearRing(polygon + .getExteriorRing().getCoordinates()), + interiorRings.toArray(new LinearRing[0])); + } + finalPolys.add(polygon); + } + + if (finalPolys.size() > 1) { + // More than one polygon resulting, create MultiPolygon + geoms.add(gf.createMultiPolygon(finalPolys + .toArray(new Polygon[0]))); } else { - double[] offsets = flattenGeometry(g); - double minOffset = offsets[0]; - double maxOffset = offsets[1]; - correct(geoms, g, inverseCentralMeridian, minOffset, - maxOffset); + // 1 or 0 polygons, just add to list + for (Polygon polygon : finalPolys) { + geoms.add(polygon); + } } - for (Geometry geom : geoms) { - rollGeometry(geom); - } - geomList.addAll(geoms); } else { - geomList.add(g); + double[] offsets = flattenGeometry(g); + correct(geoms, g, offsets); } + for (Geometry geom : geoms) { + rollGeometry(geom); + } + geomList.addAll(geoms); + } else { + geomList.add(g); } } private void correct(List geoms, Geometry flattenedGeom, - double inverseCentralMeridian, double minOffset, double maxOffset) { - if (minOffset == 0.0 && maxOffset == 0.0) { + double[] offsets) { + if (offsets == null) { // no offsets to apply, add and return geoms.add(flattenedGeom); return; } else if (flattenedGeom.isValid()) { - // Only apply world wrap correcting to valid geometries + // Only apply world wrap correcting to valid geometries, otherwise + // throw them out since we can't guarantee integrity GeometryFactory gf = flattenedGeom.getFactory(); double delta = 0.00001; - double start = inverseCentralMeridian + minOffset - 360; - double end = inverseCentralMeridian + maxOffset + 360; + double start = checker.getLowInverseCentralMeridian() + offsets[0]; + double end = checker.getHighInverseCentralMeridian() + offsets[1]; double minY = -90, maxY = 90; while (start < end) { @@ -205,7 +248,7 @@ public class WorldWrapCorrector { */ private void rollGeometry(Geometry geom) { for (Coordinate c : geom.getCoordinates()) { - while (c.x <= -180.0) { + while (c.x < -180.0) { c.x += 360.0; } while (c.x > 180.0) { @@ -221,8 +264,10 @@ public class WorldWrapCorrector { * continuous between -180/180 * * @param geom + * @return null if geometry does not need to be corrected */ private double[] flattenGeometry(Geometry geom) { + boolean handle = false; double currOffset = 0.0; double minOffset = 0.0, maxOffset = 0.0; Coordinate[] coords = geom.getCoordinates(); @@ -241,9 +286,12 @@ public class WorldWrapCorrector { low = false; } else if (b.x - a.x > 180.0) { low = true; + } else if (checker.check(a.x, b.x)) { + handle = true; } if (low != null) { + handle = true; // we wrap either low end or high if (low) { currOffset -= 360; @@ -260,7 +308,16 @@ public class WorldWrapCorrector { } } } - return new double[] { minOffset, maxOffset }; + return handle ? new double[] { minOffset, maxOffset } : null; } + private static void extractPolygons(List polygons, Geometry geom) { + if (geom instanceof Polygon) { + polygons.add((Polygon) geom); + } else if (geom instanceof GeometryCollection) { + for (int n = 0; n < geom.getNumGeometries(); ++n) { + extractPolygons(polygons, geom.getGeometryN(n)); + } + } + } }