/* * 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.spatialjoin; import com.vividsolutions.jts.geom.Geometry; import java.util.ArrayList; import java.util.logging.Level; import org.opengis.feature.AttributeType; import org.geotoolkit.data.FeatureCollection; import org.geotoolkit.data.FeatureIterator; import org.apache.sis.feature.SingleAttributeTypeBuilder; import org.geotoolkit.geometry.jts.JTS; import org.geotoolkit.processing.AbstractProcess; import org.geotoolkit.process.ProcessDescriptor; import org.geotoolkit.process.ProcessException; import org.geotoolkit.processing.vector.intersect.IntersectDescriptor; import org.geotoolkit.processing.vector.nearest.NearestDescriptor; 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.apache.sis.feature.FeatureExt; import org.apache.sis.feature.builder.FeatureTypeBuilder; import org.apache.sis.internal.feature.AttributeConvention; import org.apache.sis.util.logging.Logging; import static org.geotoolkit.parameter.Parameters.*; /** * Process return the target FeatureCollection with source FeatureCollection attributes. * The link between target and source depend of method used (Intersect or Nearest). * * @author Quentin Boileau */ public class SpatialJoinProcess extends AbstractProcess { /** * Default constructor */ public SpatialJoinProcess(final ParameterValueGroup input) { super(SpatialJoinDescriptor.INSTANCE, input); } /** * {@inheritDoc } */ @Override protected void execute() { final FeatureCollection sourceFeatureList = value(VectorDescriptor.FEATURE_IN, inputParameters); final FeatureCollection targetFeatureList = value(SpatialJoinDescriptor.FEATURE_TARGET, inputParameters); final boolean method = value(SpatialJoinDescriptor.INTERSECT, inputParameters); final FeatureCollection resultFeatureList = new SpatialJoinFeatureCollection(sourceFeatureList, targetFeatureList, method); getOrCreate(VectorDescriptor.FEATURE_OUT, outputParameters).setValue(resultFeatureList); } /** * This function join target Feature with another Feature form a source FeatureCollection. * * If boolean <code>method</code> is true, the method used is Intersect, else it's Nearest. * * If there is no Feature which Intersect the target Geometry, the return Feature * will have "joined attributes" set to null. * * If there is more than one result for Nearest method * (many Feature at the same distance), we use the first returned. * * If there is more than one result for Intersect method , we use the Feature * with the biggest intersection area with target Geometry. * * @param target the target Feature * @param newType the concatenated FeatureType * @param sourceFC the source FeatureCollection * @param method the used method. True -> Intersect, False -> Nearest * @return the joined feature */ static Feature join(final Feature target, final FeatureType newType, final FeatureCollection sourceFC, final boolean method) { Feature resultFeature = newType.newInstance(); FeatureExt.setId(resultFeature, FeatureExt.getId(target)); //copy target Feature for (final PropertyType targetProperty : target.getType().getProperties(true)) { if(targetProperty instanceof AttributeType && !AttributeConvention.contains(targetProperty.getName())){ final String name = targetProperty.getName().toString(); resultFeature.setPropertyValue(name, target.getPropertyValue(name)); } } ProcessDescriptor desc; org.geotoolkit.process.Process proc; ParameterValueGroup in; ArrayList<Feature> featureOutArray; //for each target feature geometry for (final PropertyType property : target.getType().getProperties(true)) { if (AttributeConvention.isGeometryAttribute(property)) { final Geometry targetGeometry = (Geometry) target.getPropertyValue(property.getName().toString()); final CoordinateReferenceSystem geomCRS = FeatureExt.getCRS(property); JTS.setCRS(targetGeometry, geomCRS); //add CRS to the used data geometry //use intersect method if (method) { desc = IntersectDescriptor.INSTANCE; in = desc.getInputDescriptor().createValue(); in.parameter(IntersectDescriptor.FEATURE_IN .getName().getCode()).setValue(sourceFC); in.parameter(IntersectDescriptor.GEOMETRY_IN.getName().getCode()).setValue(targetGeometry); proc = desc.createProcess(in); } else { //use nearest method desc = NearestDescriptor.INSTANCE; in = desc.getInputDescriptor().createValue(); in.parameter(NearestDescriptor.FEATURE_IN .getName().getCode()).setValue(sourceFC); in.parameter(NearestDescriptor.GEOMETRY_IN.getName().getCode()).setValue(targetGeometry); proc = desc.createProcess(in); } //run it final FeatureCollection featureOut; try { featureOut = (FeatureCollection) proc.call().parameter("feature_out").getValue(); } catch (ProcessException ex) { Logging.getLogger("org.geotoolkit.processing.vector.spatialjoin").log(Level.WARNING, null, ex); return null; } featureOutArray = new ArrayList<>(featureOut); if (method) { //intersect method if (featureOutArray.isEmpty()) { //no intersection return resultFeature; } else { if (featureOutArray.size() > 1) { //more than one intersection final Feature biggestFeature = biggestIntersection(featureOut, targetGeometry); resultFeature = copyAttributes(target, biggestFeature, newType); } else {// only one intersection resultFeature = copyAttributes(target, featureOutArray.get(0), newType); } } } else { //nearest method if (featureOutArray.isEmpty()) { return resultFeature; } else { resultFeature = copyAttributes(target, featureOutArray.get(0), newType); } } } } return resultFeature; } /** * This function copy attributes from source to target Feature except geometry descriptor. * The copied attributes name will be "attributeName_sourceFeatureTypeName". * @param target targetFeature * @param source source Feature * @param concatType concatenated FeatureType * @return the resulting Feature */ static Feature copyAttributes(final Feature target, final Feature source, final FeatureType concatType) { final Feature resultFeature = concatType.newInstance(); resultFeature.setPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString(), FeatureExt.getId(target).getID() + "_" + FeatureExt.getId(source).getID()); //copy target Feature for (final PropertyType targetProperty : target.getType().getProperties(true)) { if(targetProperty instanceof AttributeType && !AttributeConvention.contains(targetProperty.getName())){ final String name = targetProperty.getName().toString(); resultFeature.setPropertyValue(name, target.getPropertyValue(name)); } } //copy source Feature except geometry descriptor for (final PropertyType sourceProperty : source.getType().getProperties(true)) { if(sourceProperty instanceof AttributeType && !AttributeConvention.contains(sourceProperty.getName())){ if (!AttributeConvention.isGeometryAttribute(sourceProperty)) { final String name = sourceProperty.getName().tip().toString(); resultFeature.setPropertyValue(name + "_" + source.getType().getName().tip().toString(), source.getPropertyValue(name)); } } } return resultFeature; } /** * Return the Feature with the biggest intersection area with the geometry. * If there is many Feature with the same area, the function return th first founded. * @param outFC * @param intersectGeometry * @return the Feature */ static Feature biggestIntersection(final FeatureCollection outFC, final Geometry intersectGeometry) { double area = 0.0; final ArrayList<Feature> listID = new ArrayList<>(); try (final FeatureIterator iter = outFC.iterator(null)) { while (iter.hasNext()) { final Feature feature = iter.next(); for (final PropertyType property : feature.getType().getProperties(true)) { if (AttributeConvention.isGeometryAttribute(property)) { final Geometry geom = (Geometry) feature.getPropertyValue(property.getName().toString()); final double computeArea = intersectGeometry.intersection(geom).getArea(); if (computeArea > area) { listID.clear(); area = computeArea; listID.add(feature); } else { if (computeArea == area) { listID.add(feature); } } } } } } return listID.get(0); } /** * Create a new FeatureType with the target FeatureType and adding * source attributes except the geometry descriptor. * * @param targetType target FeatureType * @param sourceType source FeatureType * @return the new FeatureType */ static FeatureType concatType(final FeatureType targetType, final FeatureType sourceType) { boolean isSameName = false; //copy targetType into a FeatureTypeBuilder and change Name to targetName+sourceName final FeatureTypeBuilder ftb = new FeatureTypeBuilder(targetType); ftb.setName(targetType.getName().tip().toString() + "_" + sourceType.getName().tip().toString()); //each source descriptor for (final PropertyType sourceDesc : sourceType.getProperties(true)) { if(AttributeConvention.contains(sourceDesc.getName()) || !(sourceDesc instanceof AttributeType)) continue; //add all descriptors but geometry if (!AttributeConvention.isGeometryAttribute(sourceDesc)) { SingleAttributeTypeBuilder typeBuilder = new SingleAttributeTypeBuilder(); typeBuilder.copy((AttributeType<?>) sourceDesc); //test if exist in targetType for (final PropertyType targetDesc : sourceType.getProperties(true)) { if (targetDesc.getName() == sourceDesc.getName()) { isSameName = true; } } if (isSameName) { final String newName = sourceDesc.getName().tip().toString() + "_" + sourceType.getName().tip().toString(); typeBuilder.setName(newName); } typeBuilder.setMinimumOccurs(0); ftb.addProperty(typeBuilder.build()); } } return ftb.build(); } }