use of org.locationtech.jts.index.quadtree.Quadtree in project qupath by qupath.
the class ContourTracing method mergeGeometryWrappers.
/**
* Merge together geometries.
* @param list
* @param xBounds x tile boundaries; unioning is only applied over boundaries
* @param yBounds y tile boundaries; unioning is only applied over boundaries
* @return
*/
private static Geometry mergeGeometryWrappers(List<GeometryWrapper> list, int[] xBounds, int[] yBounds) {
// Shouldn't happy (since we should have filtered out empty lists before calling this)
if (list.isEmpty())
return GeometryTools.getDefaultFactory().createEmpty(2);
// If we just have one tile, that's what we need
if (list.size() == 1)
return list.get(0).geometry;
var factory = list.get(0).geometry.getFactory();
// Merge everything quickly into a single geometry
var allPolygons = new ArrayList<Polygon>();
for (var temp : list) PolygonExtracter.getPolygons(temp.geometry, allPolygons);
// TODO: Explore where buffering is faster than union; if we can get rules for this it can be used instead
boolean onlyBuffer = false;
Geometry geometry;
if (onlyBuffer) {
var singleGeometry = factory.buildGeometry(allPolygons);
geometry = singleGeometry.buffer(0);
} else {
// Unioning is expensive, so we just want to do it where really needed
var tree = new Quadtree();
for (var p : allPolygons) {
tree.insert(p.getEnvelopeInternal(), p);
}
var env = new Envelope();
var toMerge = new HashSet<Polygon>();
for (int yi = 1; yi < yBounds.length - 1; yi++) {
env.init(xBounds[0] - 1, xBounds[xBounds.length - 1] + 1, yBounds[yi] - 1, yBounds[yi] + 1);
var items = tree.query(env);
if (items.size() > 1)
toMerge.addAll(items);
}
for (int xi = 1; xi < xBounds.length - 1; xi++) {
env.init(xBounds[xi] - 1, xBounds[xi] + 1, yBounds[0] - 1, yBounds[yBounds.length - 1] + 1);
var items = tree.query(env);
if (items.size() > 1)
toMerge.addAll(items);
}
if (!toMerge.isEmpty()) {
logger.debug("Computing union for {}/{} polygons", toMerge.size(), allPolygons.size());
var mergedGeometry = GeometryTools.union(toMerge);
// System.err.println("To merge: " + toMerge.size());
// var mergedGeometry = factory.buildGeometry(toMerge).buffer(0);
var iter = allPolygons.iterator();
while (iter.hasNext()) {
if (toMerge.contains(iter.next()))
iter.remove();
}
allPolygons.removeAll(toMerge);
var newPolygons = new ArrayList<Polygon>();
PolygonExtracter.getPolygons(mergedGeometry, newPolygons);
allPolygons.addAll(newPolygons);
}
geometry = factory.buildGeometry(allPolygons);
geometry.normalize();
}
return geometry;
}
use of org.locationtech.jts.index.quadtree.Quadtree in project qupath by qupath.
the class PathObjectTileCache method removeFromCache.
/**
* This doesn't acquire the lock! The locking is done first.
*
* @param pathObject
* @param removeChildren
*/
private void removeFromCache(PathObject pathObject, boolean removeChildren) {
// If the cache isn't active, then nothing to remove
if (!isActive())
return;
SpatialIndex mapObjects = map.get(pathObject.getClass());
// We can remove objects from a Quadtree
if (mapObjects instanceof Quadtree) {
Envelope envelope = lastEnvelopeMap.get(pathObject);
envelope = MAX_ENVELOPE;
if (envelope != null) {
if (mapObjects.remove(envelope, pathObject)) {
logger.debug("Removed {} from cache", pathObject);
} else
logger.debug("Unable to remove {} from cache", pathObject);
} else {
logger.debug("No envelope found for {}", pathObject);
}
// Remove the children
if (removeChildren) {
for (PathObject child : pathObject.getChildObjectsAsArray()) removeFromCache(child, removeChildren);
}
} else if (mapObjects instanceof SpatialIndex && !removeChildren) {
// We can't remove objects from a STRtree, but since we're just removing one object we can rebuild only the cache for this class
constructCache(pathObject.getClass());
} else {
// If we need to remove multiple objects, better to just rebuild the entire cache
constructCache(null);
}
}
use of org.locationtech.jts.index.quadtree.Quadtree in project qupath by qupath.
the class GeometryTools method removeInteriorRings.
/**
* Fill all interior rings for the specified geometry that have an area < a specified threshold.
* <p>
* Note that this assumes that the geometry is valid, and does not contain self-intersections or overlapping pieces.
* No checks are made to confirm this (for performance reasons).
*
* @param geometry
* @param minRingArea
* @return
*/
public static Geometry removeInteriorRings(Geometry geometry, double minRingArea) {
if (minRingArea <= 0)
return geometry;
// Single polygons are easy... just remove the interior rings
if (geometry instanceof Polygon)
return removeInteriorRings((Polygon) geometry, minRingArea, null);
// // Quick check to see if we are filling all holes - this is also rather a lot easier
// if (!Double.isFinite(minRingArea))
// return fillHoles(geometry);
// Remove interior rings that are too small, logging their location in case we need it
// Also keep a list of small geometries, which might be inside rings that have been removed
var list = flatten(geometry, null);
var smallGeometries = new HashSet<Geometry>();
Quadtree tree = null;
var preparedFactory = new PreparedGeometryFactory();
var removedRingList = new ArrayList<LinearRing>();
for (int i = 0; i < list.size(); i++) {
var temp = list.get(i);
if (temp instanceof Polygon) {
var poly = removeInteriorRings((Polygon) temp, minRingArea, removedRingList);
if (poly != temp) {
// TODO: Add the holes rather than the full polygons
if (tree == null)
tree = new Quadtree();
for (var ring : removedRingList) {
var hole = preparedFactory.create(ring.getFactory().createPolygon(ring));
tree.insert(ring.getEnvelopeInternal(), hole);
}
list.set(i, poly);
removedRingList.clear();
}
// Check also if the polygon could also be swallowed by another filled hole
if (org.locationtech.jts.algorithm.Area.ofRing(poly.getExteriorRing().getCoordinateSequence()) < minRingArea)
smallGeometries.add(poly);
} else if (temp.getArea() < minRingArea)
smallGeometries.add(temp);
}
// If we don't have a tree, we didn't change anything
if (tree == null)
return geometry;
// Loop through and remove any small polygons nested inside rings that were removed
var iter = list.iterator();
while (iter.hasNext()) {
var small = iter.next();
if (smallGeometries.contains(small)) {
var query = (List<PreparedPolygon>) tree.query(small.getEnvelopeInternal());
for (PreparedPolygon hole : query) {
if (hole.covers(small)) {
iter.remove();
break;
}
// if (PointLocation.isInRing(small.getInteriorPoint().getCoordinate(), ring.getCoordinates())) {
// iter.remove();
// break;
// }
}
}
}
// Build a geometry from what is left
return geometry.getFactory().buildGeometry(list);
// var filtered = list.stream().map(g -> {
// if (g instanceof Polygon)
// return removeInteriorRings((Polygon)g, minRingArea);
// else
// return g;
// }).collect(Collectors.toList());
// TODO: Find out how to avoid the Union operation (which can be very slow)
// We need to use union because there may be polygons nested within holes that have been filled
// return GeometryTools.union(filtered);
}
Aggregations