Search in sources :

Example 1 with Quadtree

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;
}
Also used : Geometry(org.locationtech.jts.geom.Geometry) Quadtree(org.locationtech.jts.index.quadtree.Quadtree) ArrayList(java.util.ArrayList) Envelope(org.locationtech.jts.geom.Envelope) HashSet(java.util.HashSet)

Example 2 with Quadtree

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);
    }
}
Also used : Quadtree(org.locationtech.jts.index.quadtree.Quadtree) PathObject(qupath.lib.objects.PathObject) SpatialIndex(org.locationtech.jts.index.SpatialIndex) Envelope(org.locationtech.jts.geom.Envelope)

Example 3 with Quadtree

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 &lt; 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);
}
Also used : Quadtree(org.locationtech.jts.index.quadtree.Quadtree) ArrayList(java.util.ArrayList) List(java.util.List) ArrayList(java.util.ArrayList) CoordinateList(org.locationtech.jts.geom.CoordinateList) PreparedPolygon(org.locationtech.jts.geom.prep.PreparedPolygon) Polygon(org.locationtech.jts.geom.Polygon) PreparedPolygon(org.locationtech.jts.geom.prep.PreparedPolygon) Point(org.locationtech.jts.geom.Point) MultiPoint(org.locationtech.jts.geom.MultiPoint) HashSet(java.util.HashSet) PreparedGeometryFactory(org.locationtech.jts.geom.prep.PreparedGeometryFactory)

Aggregations

Quadtree (org.locationtech.jts.index.quadtree.Quadtree)3 ArrayList (java.util.ArrayList)2 HashSet (java.util.HashSet)2 Envelope (org.locationtech.jts.geom.Envelope)2 List (java.util.List)1 CoordinateList (org.locationtech.jts.geom.CoordinateList)1 Geometry (org.locationtech.jts.geom.Geometry)1 MultiPoint (org.locationtech.jts.geom.MultiPoint)1 Point (org.locationtech.jts.geom.Point)1 Polygon (org.locationtech.jts.geom.Polygon)1 PreparedGeometryFactory (org.locationtech.jts.geom.prep.PreparedGeometryFactory)1 PreparedPolygon (org.locationtech.jts.geom.prep.PreparedPolygon)1 SpatialIndex (org.locationtech.jts.index.SpatialIndex)1 PathObject (qupath.lib.objects.PathObject)1