/* Copyright (c) 2014 Boundless and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Distribution License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/edl-v10.html * * Contributors: * Gabriel Roldan (Boundless) - initial implementation */ package org.locationtech.geogig.api.plumbing.diff; import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.crs.DefaultEngineeringCRS; import org.locationtech.geogig.api.Bounded; import org.locationtech.geogig.api.Bucket; import org.locationtech.geogig.api.Node; import org.locationtech.geogig.api.ObjectId; import org.locationtech.geogig.api.RevFeatureType; import org.locationtech.geogig.api.plumbing.diff.PreOrderDiffWalk.Consumer; import org.locationtech.geogig.storage.ObjectDatabase; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.TransformException; import com.google.common.base.Optional; import com.google.common.base.Throwables; /** * A {@link Consumer} decorator that filters {@link Node nodes} by a bounding box intersection check * before delegating. */ public final class BoundsFilteringDiffConsumer extends PreOrderDiffWalk.ForwardingConsumer { private DiffPathTracker tracker = new DiffPathTracker(); private final ReferencedEnvelope boundsFilter; private ObjectDatabase ftypeSource; public BoundsFilteringDiffConsumer(ReferencedEnvelope bounds, PreOrderDiffWalk.Consumer delegate, ObjectDatabase ftypeSource) { super(delegate); this.boundsFilter = bounds; this.ftypeSource = ftypeSource; } @Override public boolean tree(Node left, Node right) { tracker.tree(left, right); if (intersects(left, right)) { return super.tree(left, right); } return false; } @Override public void endTree(Node left, Node right) { if (intersects(left, right)) { super.endTree(left, right); } tracker.endTree(left, right); } @Override public boolean bucket(int bucketIndex, int bucketDepth, Bucket left, Bucket right) { if (intersects(left, right)) { return super.bucket(bucketIndex, bucketDepth, left, right); } return false; } @Override public void endBucket(int bucketIndex, int bucketDepth, Bucket left, Bucket right) { if (intersects(left, right)) { super.endBucket(bucketIndex, bucketDepth, left, right); } } @Override public void feature(Node left, Node right) { if (intersects(left, right)) { super.feature(left, right); } } private boolean intersects(Bounded left, Bounded right) { return intersects(left, tracker.currentLeftMetadataId()) || intersects(right, tracker.currentRightMetadataId()); } private boolean intersects(@Nullable Bounded node, final Optional<ObjectId> metadataId) { if (node == null) { return false; } if (!metadataId.isPresent()) { return true; } ReferencedEnvelope nativeCrsFilter = getProjectedFilter(metadataId.get()); boolean intersects = node.intersects(nativeCrsFilter); return intersects; } private Map<ObjectId, ReferencedEnvelope> filtersByMetadataId = new HashMap<>(); private ReferencedEnvelope getProjectedFilter(final ObjectId metadataId) { ReferencedEnvelope projectedFilter = filtersByMetadataId.get(metadataId); if (projectedFilter == null) { projectedFilter = createProjectedFilter(metadataId); filtersByMetadataId.put(metadataId, projectedFilter); } return projectedFilter; } private ReferencedEnvelope createProjectedFilter(ObjectId metadataId) { final ReferencedEnvelope boundsFilter = this.boundsFilter; RevFeatureType featureType = ftypeSource.getFeatureType(metadataId); CoordinateReferenceSystem nativeCrs = featureType.type().getCoordinateReferenceSystem(); if (null == nativeCrs || nativeCrs instanceof DefaultEngineeringCRS) { return boundsFilter; } ReferencedEnvelope transformedFilter; try { transformedFilter = boundsFilter.transform(nativeCrs, true); } catch (TransformException | FactoryException e) { throw Throwables.propagate(e); } return transformedFilter; } }