/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2011, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.processing.vector.union;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.geotoolkit.data.FeatureStoreUtilities;
import org.geotoolkit.data.FeatureCollection;
import org.geotoolkit.data.FeatureIterator;
import org.geotoolkit.processing.AbstractProcess;
import org.geotoolkit.processing.vector.VectorProcessUtils;
import org.apache.sis.referencing.CommonCRS;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.feature.PropertyType;
import org.geotoolkit.processing.vector.VectorDescriptor;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
import org.apache.sis.feature.FeatureExt;
import org.apache.sis.feature.builder.AttributeRole;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.internal.feature.AttributeConvention;
import static org.geotoolkit.parameter.Parameters.*;
import org.opengis.feature.AttributeType;
/**
* Process compute union between two FeatureCollection
* It is usually called "Spatial OR".
* The returned Features will have both attributes from the two FeatureType of FeatureCollection.
* But only one Geometry (intersection Geometry between two Features from each Collection)
* Feature ID will be formed with the two Features ID which intersects. Or if the Feature Geometry haven't
* intersection with the other FeatureCollection Geometry, he keep all of his attributes.
*
* @author Quentin Boileau
* @module
*/
public class UnionProcess extends AbstractProcess {
/**
* Default constructor
*/
public UnionProcess(final ParameterValueGroup input) {
super(UnionDescriptor.INSTANCE, input);
}
/**
* {@inheritDoc }
*/
@Override
protected void execute() {
final FeatureCollection inputFeatureList = value(VectorDescriptor.FEATURE_IN, inputParameters);
final FeatureCollection unionFeatureList = value(UnionDescriptor.FEATURE_UNION, inputParameters);
final String inputGeometryName = value(UnionDescriptor.INPUT_GEOMETRY_NAME, inputParameters);
final String unionGeometryName = value(UnionDescriptor.UNION_GEOMETRY_NAME, inputParameters);
final FeatureCollection resultFeatureList = new UnionFeatureCollection(inputFeatureList, unionFeatureList, inputGeometryName, unionGeometryName);
getOrCreate(VectorDescriptor.FEATURE_OUT, outputParameters).setValue(resultFeatureList);
}
/**
* Generate an union FeatureCollection comparing a Feature to a FeatureCollection.
* During the second pass, we remove duplicates Features
*
* @param newFeatureType the new FeatureType
* @param unionFC union FeatureCollection
* @param inputGeomName attribute name of the used Geometry from inputFeature
* @param unionGeomName attribute name of the used Geometry from unionFC
* @param featureList Set of already created Features (it's used in order to remove duplicate Features)
* @return the result FeatureCollection of an union between a Feature and a FeatureCollection
*/
static FeatureCollection unionFeatureToFC(final Feature inputFeature, final FeatureType newFeatureType, final FeatureCollection unionFC,
final String inputGeomName, final String unionGeomName, final boolean firstPass, final Set<String> featureList)
throws TransformException, FactoryException
{
final FeatureCollection resultFeatureList =
FeatureStoreUtilities.collection(FeatureExt.getId(inputFeature).getID(), newFeatureType);
/*
* In order to get all part of Feature, add a second pass with the diffenrence between the FeatureGeometry
* and united intersections. if return nothing we have all the geometry feature, else we add the difference
*/
Geometry inputGeometry = new GeometryFactory().buildGeometry(Collections.EMPTY_LIST);
for (final PropertyType inputProperty : inputFeature.getType().getProperties(true)) {
if (AttributeConvention.isGeometryAttribute(inputProperty)) {
final String name = inputProperty.getName().tip().toString();
if (name.equals(inputGeomName)) {
inputGeometry = (Geometry) inputFeature.getPropertyValue(name);
}
}
}
Geometry remainingGeometry = inputGeometry;
boolean isIntersected = false;
//Check if each union Features intersect inputFeature. if yes, create a new Feature which is union of both
try (final FeatureIterator unionIter = unionFC.iterator()) {
while (unionIter.hasNext()) {
final Feature unionFeature = unionIter.next();
final String featureID;
//Invert ID order for the second pass (firstpass "inputID U unionID", second pass "unionID U inputID")
if (firstPass) {
featureID = FeatureExt.getId(inputFeature).getID() + "-" + FeatureExt.getId(unionFeature).getID();
} else {
featureID = FeatureExt.getId(unionFeature).getID() + "-" + FeatureExt.getId(inputFeature).getID();
}
final Feature resultFeature = unionFeatureToFeature(inputFeature, unionFeature, newFeatureType,
inputGeomName, unionGeomName, featureID, firstPass);
//If resultFeature is null, mean there is no intersection
//Else we add the resutl Feature to resultFeatureList
if (resultFeature != null) {
isIntersected = true;
resultFeatureList.add(resultFeature);
Geometry intersectGeom = (Geometry) resultFeature.getPropertyValue(AttributeConvention.GEOMETRY_PROPERTY.toString());
remainingGeometry = remainingGeometry.difference(intersectGeom);
}
}
}
//If remaining Geometry is empty and isIntersecting boolean is false, mean the geometry
if (remainingGeometry.isEmpty() && !isIntersected) {
final Feature remainingFeature = newFeatureType.newInstance();
FeatureExt.setId(remainingFeature, FeatureExt.getId(inputFeature));
//Copy none Geometry attributes
for (final PropertyType inputProperty : inputFeature.getType().getProperties(true)) {
if(!(inputProperty instanceof AttributeType) || AttributeConvention.contains(inputProperty.getName())) continue;
if (!AttributeConvention.isGeometryAttribute(inputProperty)) {
final String name = inputProperty.getName().toString();
remainingFeature.setPropertyValue(name, inputFeature.getPropertyValue(name));
}
}
if (firstPass) {
remainingFeature.setPropertyValue(inputGeomName, inputGeometry);
} else {
remainingFeature.setPropertyValue(unionGeomName, inputGeometry);
}
resultFeatureList.add(remainingFeature);
}
//Create a remaining Feature with the inputGeometry
if (!(remainingGeometry.isEmpty())) {
if(remainingGeometry.equalsTopo(inputGeometry)) {
remainingGeometry = inputGeometry;
}
final Feature remainingFeature = newFeatureType.newInstance();
FeatureExt.setId(remainingFeature, FeatureExt.getId(inputFeature));
//Copy none Geometry attributes
for (final PropertyType inputProperty : inputFeature.getType().getProperties(true)) {
if(!(inputProperty instanceof AttributeType) || AttributeConvention.contains(inputProperty.getName())) continue;
if (!AttributeConvention.isGeometryAttribute(inputProperty)) {
final String name = inputProperty.getName().toString();
remainingFeature.setPropertyValue(name, inputFeature.getPropertyValue(name));
}
}
//System.out.println("LOG Empty remaining Geometry");
if (firstPass) {
remainingFeature.setPropertyValue(inputGeomName, remainingGeometry);
} else {
remainingFeature.setPropertyValue(unionGeomName, remainingGeometry);
}
resultFeatureList.add(remainingFeature);
}
final Collection<Feature> featureToRemove = new ArrayList<>();
/* Check if created features are already present in featureList.
* If yes, we delete them from the returning FeatureCollection
* else we add them into the featureList
*/
for (final Feature createdFeature : resultFeatureList) {
final String createdFeatureID = FeatureExt.getId(createdFeature).getID();
if (featureList.contains(createdFeatureID)) {
featureToRemove.add(createdFeature);
} else {
featureList.add(createdFeatureID);
}
}
//remove existing feature
resultFeatureList.removeAll(featureToRemove);
return resultFeatureList;
}
/**
* Create a Feature which is the union between two Features. If there is no intersection,
* function will return null.
*
* @param inputGeomName attribute name of the used Geometry in inputFeature
* @param unionGeomName attribute name of the used Geometry in unionFeature
* @param featureID ID of the new Feature
* @param firstPass boolean to set which Geometry is use for the created Feature
* @return the union Feature with intersection as Geometry. Return null if there is no intersection.
*/
private static Feature unionFeatureToFeature(final Feature inputFeature, final Feature unionFeature, final FeatureType newFeatureType,
final String inputGeomName, final String unionGeomName, final String featureID, final boolean firstPass) throws FactoryException,
TransformException
{
final Geometry intersectGeometry = VectorProcessUtils.intersectionFeatureToFeature(inputFeature, unionFeature, inputGeomName, unionGeomName);
if (!intersectGeometry.isEmpty()) {
final Feature resultFeature = newFeatureType.newInstance();
resultFeature.setPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString(), featureID);
//copy none Geometry attributes
for (final PropertyType unionProperty : unionFeature.getType().getProperties(true)) {
if(!(unionProperty instanceof AttributeType) || AttributeConvention.contains(unionProperty.getName())) continue;
if (!AttributeConvention.isGeometryAttribute(unionProperty)) {
final String name = unionProperty.getName().toString();
resultFeature.setPropertyValue(name, unionFeature.getPropertyValue(name));
}
}
for (final PropertyType inputProperty : inputFeature.getType().getProperties(true)) {
if(!(inputProperty instanceof AttributeType) || AttributeConvention.contains(inputProperty.getName())) continue;
if (!AttributeConvention.isGeometryAttribute(inputProperty)) {
final String name = inputProperty.getName().toString();
resultFeature.setPropertyValue(name, inputFeature.getPropertyValue(name));
}
}
if (firstPass) {
resultFeature.setPropertyValue(inputGeomName, intersectGeometry);
} else {
resultFeature.setPropertyValue(unionGeomName, intersectGeometry);
}
return resultFeature;
} else {
return null;
}
}
/**
* Create a new FeatureType merging two inputs FeatureType with only one Geometry attribute.
* By default the geometry CRS is set to WGS84
* @param type1
* @param type2
* @param geometryName - Name of the union Geometry
* @param geometryCRS - CRS of the union Geometry
* @return the new FeatureType
*/
static FeatureType mergeType(final FeatureType type1, final FeatureType type2, final String geometryName,
CoordinateReferenceSystem geometryCRS) {
//use WGS84 CRS if geometryCRS is null
if (geometryCRS == null) {
geometryCRS = CommonCRS.WGS84.normalizedGeographic();
}
final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
//add identifier
ftb.addAttribute(String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY).addRole(AttributeRole.IDENTIFIER_COMPONENT);
// Name of the new FeatureType.
ftb.setName(type1.getName().tip().toString() + "-" + type2.getName().tip().toString());
//Copy all properties from type1
for (final PropertyType sourceDesc : type1.getProperties(true)) {
if(AttributeConvention.contains(sourceDesc.getName())) continue;
//add all descriptors but geometry
if (!AttributeConvention.isGeometryAttribute(sourceDesc)) {
ftb.addProperty(sourceDesc);
}
}
// Copy all properties from the type2 without duplicate
for (final PropertyType targetDesc : type2.getProperties(true)) {
if(AttributeConvention.contains(targetDesc.getName())) continue;
if (!AttributeConvention.isGeometryAttribute(targetDesc)) {
boolean isExistDesc = false;
//search if target descriptor name already exist into source descriptors
for (final PropertyType sourceDesc : type1.getProperties(true)) {
if (!AttributeConvention.isGeometryAttribute(sourceDesc)) {
//if attribute descriptor name isn't found into ftb we add it
if ((targetDesc.getName().equals(sourceDesc.getName()))) {
isExistDesc = true;
break;
}
}
}
if (!isExistDesc) {
ftb.addProperty(targetDesc);
}
}
}
//add geometry
ftb.addAttribute(Geometry.class).setName(geometryName).setCRS(geometryCRS).addRole(AttributeRole.DEFAULT_GEOMETRY);
return ftb.build();
}
}