/* Copyright (c) 2013-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:
* Victor Olaya (Boundless) - initial implementation
*/
package org.locationtech.geogig.api.plumbing.merge;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.locationtech.geogig.api.AbstractGeoGigOp;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.RevFeature;
import org.locationtech.geogig.api.RevFeatureType;
import org.locationtech.geogig.api.plumbing.RevObjectParse;
import org.locationtech.geogig.api.plumbing.diff.GeometryAttributeDiff;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.PropertyDescriptor;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.vividsolutions.jts.geom.Geometry;
/**
* This operation merges two features that have compatible changes, returning the result of this
* automatic merging. Features must have the same schema
*
* No checking is performed to see that changes are actually compatible, so this should be done in
* advance. If that's not the case, the merged result might have lost some changes made on one of
* the features to merge, which will be overwritten by changes in the other one
*
*/
public class MergeFeaturesOp extends AbstractGeoGigOp<Feature> {
private NodeRef nodeRefB;
private NodeRef nodeRefA;
private NodeRef ancestorRef;
@Override
protected Feature _call() {
checkNotNull(nodeRefA, "first feature version not specified");
checkNotNull(nodeRefB, "second feature version not specified");
checkNotNull(ancestorRef, "ancestor version not specified");
// String firstPath = removeRef(oldNodeRef.path());
// String newPath = removeRef(newNodeRef.path());
checkArgument(nodeRefA.path().equals(nodeRefB.path()),
"old and new versions do not correspond to the same feature");
Optional<RevFeature> featureA = command(RevObjectParse.class).setObjectId(
nodeRefA.getNode().getObjectId()).call(RevFeature.class);
checkArgument(featureA.isPresent(), "Invalid reference: %s", nodeRefA);
Optional<RevFeature> featureB = command(RevObjectParse.class).setObjectId(
nodeRefB.getNode().getObjectId()).call(RevFeature.class);
checkArgument(featureB.isPresent(), "Invalid reference: %s", nodeRefB);
Optional<RevFeature> ancestorFeature = command(RevObjectParse.class).setObjectId(
ancestorRef.getNode().getObjectId()).call(RevFeature.class);
checkArgument(ancestorFeature.isPresent(), "Invalid reference: %s", ancestorRef);
Optional<RevFeatureType> featureTypeA = command(RevObjectParse.class).setObjectId(
nodeRefA.getMetadataId()).call(RevFeatureType.class);
checkArgument(featureTypeA.isPresent(), "Invalid reference: %s", nodeRefA);
Optional<RevFeatureType> featureTypeB = command(RevObjectParse.class).setObjectId(
nodeRefB.getMetadataId()).call(RevFeatureType.class);
checkArgument(featureTypeB.isPresent(), "Invalid reference: %s", nodeRefB);
Optional<RevFeatureType> ancestorFeatureType = command(RevObjectParse.class).setObjectId(
ancestorRef.getMetadataId()).call(RevFeatureType.class);
checkArgument(ancestorFeatureType.isPresent(), "Invalid reference: %s", ancestorRef);
Preconditions.checkArgument(
featureTypeA.equals(featureTypeB) && featureTypeA.equals(ancestorFeatureType),
"Non-matching feature types. Cannot merge");
return merge(featureA.get(), featureB.get(), ancestorFeature.get(),
ancestorFeatureType.get());
}
@SuppressWarnings("unchecked")
private Feature merge(RevFeature featureA, RevFeature featureB, RevFeature ancestor,
RevFeatureType featureType) {
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(
(SimpleFeatureType) featureType.type());
ImmutableList<Optional<Object>> valuesA = featureA.getValues();
ImmutableList<Optional<Object>> valuesB = featureB.getValues();
ImmutableList<Optional<Object>> valuesAncestor = ancestor.getValues();
ImmutableList<PropertyDescriptor> descriptors = featureType.sortedDescriptors();
for (int i = 0; i < descriptors.size(); i++) {
PropertyDescriptor descriptor = descriptors.get(i);
boolean isGeom = Geometry.class.isAssignableFrom(descriptor.getType().getBinding());
Name name = descriptor.getName();
Optional<Object> valueAncestor = valuesAncestor.get(i);
Optional<Object> valueA = valuesA.get(i);
Optional<Object> valueB = valuesB.get(i);
if (!valueA.equals(valueAncestor)) {
Optional<Object> merged = valueA;
if (isGeom && !valueB.equals(valueAncestor)) { // true merge is only done with
// geometries
GeometryAttributeDiff diffB = new GeometryAttributeDiff(
Optional.fromNullable((Geometry) valueAncestor.orNull()),
Optional.fromNullable((Geometry) valueB.orNull()));
merged = (Optional<Object>) diffB.applyOn(valueA);
}
featureBuilder.set(name, merged.orNull());
} else {
featureBuilder.set(name, valueB.orNull());
}
}
return featureBuilder.buildFeature(nodeRefA.name());
}
public MergeFeaturesOp setFirstFeature(NodeRef feature) {
this.nodeRefA = feature;
return this;
}
public MergeFeaturesOp setSecondFeature(NodeRef feature) {
this.nodeRefB = feature;
return this;
}
public MergeFeaturesOp setAncestorFeature(NodeRef ancestor) {
this.ancestorRef = ancestor;
return this;
}
}