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:5a7ed945e2
[formerly9d5a7cc39c
] [formerly291871bbad
[formerly 7ca7360f261e199c813f3a48d7d0faabada61dba]] Former-commit-id:291871bbad
Former-commit-id:b300507bbc
This commit is contained in:
parent
9965ae1281
commit
b260613a4c
3 changed files with 141 additions and 75 deletions
|
@ -356,16 +356,16 @@ public abstract class AbstractGLMesh implements IMesh {
|
||||||
// Get various x distances to use as weights in interpolating
|
// Get various x distances to use as weights in interpolating
|
||||||
double abDist = 360 - Math.abs(ax - bx);
|
double abDist = 360 - Math.abs(ax - bx);
|
||||||
double acDist = 360 - Math.abs(ax - cx);
|
double acDist = 360 - Math.abs(ax - cx);
|
||||||
double amDist = 360 + ax - wwc.getInverseCentralMeridian();
|
double amDist = ax - wwc.getLowInverseCentralMeridian();
|
||||||
if (amDist > 360) {
|
if (amDist > 360) {
|
||||||
amDist = amDist - 360;
|
amDist = amDist - 360;
|
||||||
}
|
}
|
||||||
// x location to use for midpoints on the triangle side, should be on
|
// x location to use for midpoints on the triangle side, should be on
|
||||||
// same side of central meridian as a
|
// 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
|
// x location to use for midpoints on the quad side, should be on
|
||||||
// same side of central meridian as b and c
|
// 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
|
// If a is closer to the central meridian on the other side then switch
|
||||||
// amDist, tx, and qx
|
// amDist, tx, and qx
|
||||||
if (amDist > 180) {
|
if (amDist > 180) {
|
||||||
|
|
|
@ -51,9 +51,9 @@ import com.raytheon.uf.common.status.UFStatus.Priority;
|
||||||
|
|
||||||
public class WorldWrapChecker {
|
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;
|
private boolean checkForWrapping = false;
|
||||||
|
|
||||||
|
@ -67,16 +67,14 @@ public class WorldWrapChecker {
|
||||||
AbstractProvider.CENTRAL_MERIDIAN.getName().getCode())
|
AbstractProvider.CENTRAL_MERIDIAN.getName().getCode())
|
||||||
.doubleValue();
|
.doubleValue();
|
||||||
}
|
}
|
||||||
inverseCentralMeridian = centralMeridian + 180.0;
|
|
||||||
if (inverseCentralMeridian > 180.0) {
|
|
||||||
inverseCentralMeridian -= 360.0;
|
|
||||||
low = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
double l1 = inverseCentralMeridian - .1;
|
highInverseCentralMeridian = centralMeridian + 180.0;
|
||||||
double l2 = inverseCentralMeridian - .2;
|
lowInverseCentralMeridian = centralMeridian - 180.0;
|
||||||
double r1 = inverseCentralMeridian - 359.9;
|
|
||||||
double r2 = inverseCentralMeridian - 359.8;
|
double l1 = highInverseCentralMeridian - .1;
|
||||||
|
double l2 = highInverseCentralMeridian - .2;
|
||||||
|
double r1 = highInverseCentralMeridian - 359.9;
|
||||||
|
double r2 = highInverseCentralMeridian - 359.8;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MathTransform latLonToGrid = new DefaultMathTransformFactory()
|
MathTransform latLonToGrid = new DefaultMathTransformFactory()
|
||||||
|
@ -121,15 +119,26 @@ public class WorldWrapChecker {
|
||||||
return Math.abs(aLon - bLon) > 180.0;
|
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) {
|
public double toProjectionRange(double aLon) {
|
||||||
if (low && aLon < inverseCentralMeridian) {
|
while (aLon < lowInverseCentralMeridian) {
|
||||||
aLon += 360;
|
aLon += 360.0;
|
||||||
} else if (!low && aLon > inverseCentralMeridian) {
|
}
|
||||||
aLon -= 360;
|
while (aLon > highInverseCentralMeridian) {
|
||||||
|
aLon -= 360.0;
|
||||||
}
|
}
|
||||||
return aLon;
|
return aLon;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
package com.raytheon.uf.common.geospatial.util;
|
package com.raytheon.uf.common.geospatial.util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.geotools.coverage.grid.GeneralGridGeometry;
|
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.GeometryCollection;
|
||||||
import com.vividsolutions.jts.geom.GeometryFactory;
|
import com.vividsolutions.jts.geom.GeometryFactory;
|
||||||
import com.vividsolutions.jts.geom.LineString;
|
import com.vividsolutions.jts.geom.LineString;
|
||||||
|
import com.vividsolutions.jts.geom.LinearRing;
|
||||||
import com.vividsolutions.jts.geom.Polygon;
|
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
|
* This class uses the WorldWrapChecker to correct wrapping of geometries that
|
||||||
|
@ -76,7 +81,7 @@ public class WorldWrapCorrector {
|
||||||
if (checker.needsChecking() == false) {
|
if (checker.needsChecking() == false) {
|
||||||
geoms.add(geom);
|
geoms.add(geom);
|
||||||
} else {
|
} else {
|
||||||
wrapCorrect(geom, geoms, checker.getInverseCentralMeridian());
|
wrapCorrect(geom, geoms);
|
||||||
}
|
}
|
||||||
return geom.getFactory().createGeometryCollection(
|
return geom.getFactory().createGeometryCollection(
|
||||||
geoms.toArray(new Geometry[geoms.size()]));
|
geoms.toArray(new Geometry[geoms.size()]));
|
||||||
|
@ -90,11 +95,10 @@ public class WorldWrapCorrector {
|
||||||
* @param g
|
* @param g
|
||||||
* @param geomList
|
* @param geomList
|
||||||
*/
|
*/
|
||||||
private void wrapCorrect(Geometry g, List<Geometry> geomList,
|
private void wrapCorrect(Geometry g, List<Geometry> geomList) {
|
||||||
double inverseCentralMeridian) {
|
|
||||||
if (g instanceof GeometryCollection) {
|
if (g instanceof GeometryCollection) {
|
||||||
for (int n = 0; n < g.getNumGeometries(); ++n) {
|
for (int n = 0; n < g.getNumGeometries(); ++n) {
|
||||||
wrapCorrect(g.getGeometryN(n), geomList, inverseCentralMeridian);
|
wrapCorrect(g.getGeometryN(n), geomList);
|
||||||
}
|
}
|
||||||
} else if (g.isEmpty() == false) {
|
} else if (g.isEmpty() == false) {
|
||||||
// Algorithm:
|
// Algorithm:
|
||||||
|
@ -108,71 +112,110 @@ public class WorldWrapCorrector {
|
||||||
// we split it up into sections by intersecting with a 360 deg
|
// we split it up into sections by intersecting with a 360 deg
|
||||||
// inverse central meridian. We then normalize the points for each
|
// inverse central meridian. We then normalize the points for each
|
||||||
// section back to -180 to 180
|
// section back to -180 to 180
|
||||||
if (checker.needsChecking()) {
|
List<Geometry> geoms = new ArrayList<Geometry>();
|
||||||
List<Geometry> geoms = new ArrayList<Geometry>();
|
if (g instanceof Polygon) {
|
||||||
if (g instanceof Polygon) {
|
GeometryFactory gf = g.getFactory();
|
||||||
GeometryFactory gf = g.getFactory();
|
Polygon p = (Polygon) g;
|
||||||
Polygon p = (Polygon) g;
|
LineString extRing = p.getExteriorRing();
|
||||||
LineString extRing = p.getExteriorRing();
|
Polygon extPolygon = gf.createPolygon(
|
||||||
Polygon extPolygon = gf
|
gf.createLinearRing(extRing.getCoordinates()), null);
|
||||||
.createPolygon(gf.createLinearRing(extRing
|
// World wrap correct exterior ring and extract polygons
|
||||||
.getCoordinates()), null);
|
double[] offsets = flattenGeometry(extPolygon);
|
||||||
double[] offsets = flattenGeometry(extPolygon);
|
List<Geometry> extRings = new ArrayList<Geometry>();
|
||||||
List<Geometry> extRings = new ArrayList<Geometry>();
|
correct(extRings, extPolygon, offsets);
|
||||||
correct(extRings, extPolygon, inverseCentralMeridian,
|
List<Polygon> polygons = new ArrayList<Polygon>();
|
||||||
offsets[0], offsets[1]);
|
for (Geometry geom : extRings) {
|
||||||
List<Geometry> intRings = new ArrayList<Geometry>(
|
extractPolygons(polygons, geom);
|
||||||
p.getNumInteriorRing());
|
}
|
||||||
for (int n = 0; n < p.getNumInteriorRing(); ++n) {
|
|
||||||
Polygon intRing = gf.createPolygon(gf
|
// World wrap correct each interior ring
|
||||||
.createLinearRing(p.getInteriorRingN(n)
|
List<Geometry> intRings = new ArrayList<Geometry>(
|
||||||
.getCoordinates()), null);
|
p.getNumInteriorRing());
|
||||||
offsets = flattenGeometry(intRing);
|
for (int n = 0; n < p.getNumInteriorRing(); ++n) {
|
||||||
correct(intRings, intRing, inverseCentralMeridian,
|
Polygon intRing = gf.createPolygon(gf.createLinearRing(p
|
||||||
offsets[0], offsets[1]);
|
.getInteriorRingN(n).getCoordinates()), null);
|
||||||
}
|
offsets = flattenGeometry(intRing);
|
||||||
for (Geometry ext : extRings) {
|
correct(intRings, intRing, offsets);
|
||||||
for (int n1 = 0; n1 < ext.getNumGeometries(); ++n1) {
|
}
|
||||||
Geometry geom = ext.getGeometryN(n1);
|
|
||||||
for (Geometry intRing : intRings) {
|
// Extract polygons and "preprare" them for intersections
|
||||||
for (int n2 = 0; n2 < intRing
|
List<Polygon> interiorPolygons = new LinkedList<Polygon>();
|
||||||
.getNumGeometries(); ++n2) {
|
for (Geometry geom : intRings) {
|
||||||
geom = geom.difference(intRing
|
extractPolygons(interiorPolygons, geom);
|
||||||
.getGeometryN(n2));
|
}
|
||||||
}
|
List<PreparedGeometry> preparedInteriorPolygons = new LinkedList<PreparedGeometry>();
|
||||||
}
|
for (Polygon intPoly : interiorPolygons) {
|
||||||
geoms.add(geom);
|
preparedInteriorPolygons.add(PreparedGeometryFactory
|
||||||
|
.prepare(intPoly));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final polygon list (may create multipolygon out of)
|
||||||
|
List<Polygon> finalPolys = new ArrayList<Polygon>(
|
||||||
|
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<LinearRing> interiorRings = new ArrayList<LinearRing>();
|
||||||
|
Iterator<PreparedGeometry> 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 {
|
} else {
|
||||||
double[] offsets = flattenGeometry(g);
|
// 1 or 0 polygons, just add to list
|
||||||
double minOffset = offsets[0];
|
for (Polygon polygon : finalPolys) {
|
||||||
double maxOffset = offsets[1];
|
geoms.add(polygon);
|
||||||
correct(geoms, g, inverseCentralMeridian, minOffset,
|
}
|
||||||
maxOffset);
|
|
||||||
}
|
}
|
||||||
for (Geometry geom : geoms) {
|
|
||||||
rollGeometry(geom);
|
|
||||||
}
|
|
||||||
geomList.addAll(geoms);
|
|
||||||
} else {
|
} 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<Geometry> geoms, Geometry flattenedGeom,
|
private void correct(List<Geometry> geoms, Geometry flattenedGeom,
|
||||||
double inverseCentralMeridian, double minOffset, double maxOffset) {
|
double[] offsets) {
|
||||||
if (minOffset == 0.0 && maxOffset == 0.0) {
|
if (offsets == null) {
|
||||||
// no offsets to apply, add and return
|
// no offsets to apply, add and return
|
||||||
geoms.add(flattenedGeom);
|
geoms.add(flattenedGeom);
|
||||||
return;
|
return;
|
||||||
} else if (flattenedGeom.isValid()) {
|
} 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();
|
GeometryFactory gf = flattenedGeom.getFactory();
|
||||||
double delta = 0.00001;
|
double delta = 0.00001;
|
||||||
double start = inverseCentralMeridian + minOffset - 360;
|
double start = checker.getLowInverseCentralMeridian() + offsets[0];
|
||||||
double end = inverseCentralMeridian + maxOffset + 360;
|
double end = checker.getHighInverseCentralMeridian() + offsets[1];
|
||||||
double minY = -90, maxY = 90;
|
double minY = -90, maxY = 90;
|
||||||
|
|
||||||
while (start < end) {
|
while (start < end) {
|
||||||
|
@ -205,7 +248,7 @@ public class WorldWrapCorrector {
|
||||||
*/
|
*/
|
||||||
private void rollGeometry(Geometry geom) {
|
private void rollGeometry(Geometry geom) {
|
||||||
for (Coordinate c : geom.getCoordinates()) {
|
for (Coordinate c : geom.getCoordinates()) {
|
||||||
while (c.x <= -180.0) {
|
while (c.x < -180.0) {
|
||||||
c.x += 360.0;
|
c.x += 360.0;
|
||||||
}
|
}
|
||||||
while (c.x > 180.0) {
|
while (c.x > 180.0) {
|
||||||
|
@ -221,8 +264,10 @@ public class WorldWrapCorrector {
|
||||||
* continuous between -180/180
|
* continuous between -180/180
|
||||||
*
|
*
|
||||||
* @param geom
|
* @param geom
|
||||||
|
* @return null if geometry does not need to be corrected
|
||||||
*/
|
*/
|
||||||
private double[] flattenGeometry(Geometry geom) {
|
private double[] flattenGeometry(Geometry geom) {
|
||||||
|
boolean handle = false;
|
||||||
double currOffset = 0.0;
|
double currOffset = 0.0;
|
||||||
double minOffset = 0.0, maxOffset = 0.0;
|
double minOffset = 0.0, maxOffset = 0.0;
|
||||||
Coordinate[] coords = geom.getCoordinates();
|
Coordinate[] coords = geom.getCoordinates();
|
||||||
|
@ -241,9 +286,12 @@ public class WorldWrapCorrector {
|
||||||
low = false;
|
low = false;
|
||||||
} else if (b.x - a.x > 180.0) {
|
} else if (b.x - a.x > 180.0) {
|
||||||
low = true;
|
low = true;
|
||||||
|
} else if (checker.check(a.x, b.x)) {
|
||||||
|
handle = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (low != null) {
|
if (low != null) {
|
||||||
|
handle = true;
|
||||||
// we wrap either low end or high
|
// we wrap either low end or high
|
||||||
if (low) {
|
if (low) {
|
||||||
currOffset -= 360;
|
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<Polygon> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue