/* * Copyright (c) 2013 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.cst.functions.geometric.aggregate; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import com.google.common.base.Function; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import eu.esdihumboldt.hale.common.align.model.Cell; import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition; import eu.esdihumboldt.hale.common.align.transformation.engine.TransformationEngine; import eu.esdihumboldt.hale.common.align.transformation.function.PropertyValue; import eu.esdihumboldt.hale.common.align.transformation.function.TransformationException; import eu.esdihumboldt.hale.common.align.transformation.function.impl.AbstractSingleTargetPropertyTransformation; import eu.esdihumboldt.hale.common.align.transformation.function.impl.NoResultException; import eu.esdihumboldt.hale.common.align.transformation.report.TransformationLog; import eu.esdihumboldt.hale.common.align.transformation.report.impl.TransformationMessageImpl; import eu.esdihumboldt.hale.common.instance.geometry.DefaultGeometryProperty; import eu.esdihumboldt.hale.common.instance.geometry.GeometryFinder; import eu.esdihumboldt.hale.common.instance.helper.DepthFirstInstanceTraverser; import eu.esdihumboldt.hale.common.instance.helper.InstanceTraverser; import eu.esdihumboldt.hale.common.schema.geometry.CRSDefinition; import eu.esdihumboldt.hale.common.schema.geometry.GeometryProperty; import eu.esdihumboldt.util.geometry.CurveHelper; /** * Aggregates input geometries if possible. * * @author Simon Templer */ public class AggregateTransformation extends AbstractSingleTargetPropertyTransformation<TransformationEngine> implements AggregateFunction { @Override protected Object evaluate(String transformationIdentifier, TransformationEngine engine, ListMultimap<String, PropertyValue> variables, String resultName, PropertyEntityDefinition resultProperty, Map<String, String> executionParameters, TransformationLog log) throws TransformationException, NoResultException { Iterable<Object> geometries = Iterables.transform(variables.get(null), new Function<PropertyValue, Object>() { @Override public Object apply(PropertyValue input) { return input.getValue(); } }); return aggregateGeometries(geometries, log, getCell()); } /** * Aggregates geometries contained in the provided objects. * * @param geometries the geometries or instances containing geometries * @param cell the currently process cell or <code>null</code> * @param log the transformation log or <code>null</code> * @return the aggregated geometry * @throws TransformationException if source geometries don't have a common * CRS * @throws NoResultException if the result extent would be <code>null</code> */ public static GeometryProperty<?> aggregateGeometries(Iterable<?> geometries, @Nullable TransformationLog log, @Nullable Cell cell) throws NoResultException, TransformationException { InstanceTraverser traverser = new DepthFirstInstanceTraverser(true); GeometryFinder geoFind = new GeometryFinder(null); CRSDefinition commonCrs = null; Class<? extends Geometry> commonGeometryType = null; List<Geometry> collectedGeometries = new ArrayList<>(); for (Object value : geometries) { // find contained geometries traverser.traverse(value, geoFind); for (GeometryProperty<?> geom : geoFind.getGeometries()) { // check CRS // no CRS or one common CRS is OK if (commonCrs == null) { commonCrs = geom.getCRSDefinition(); } else { if (geom.getCRSDefinition() != null && !geom.getCRSDefinition().equals(commonCrs)) { // CRS doesn't match throw new TransformationException( "Source geometries don't have a common CRS."); } } Geometry g = geom.getGeometry(); // determine common geometry type: point / line / polygon if (commonGeometryType == null) { commonGeometryType = getContainedGeometryType(g.getClass()); } else { Class<? extends Geometry> currentType = getContainedGeometryType(g.getClass()); if (!commonGeometryType.isAssignableFrom(currentType)) { if (currentType.isAssignableFrom(commonGeometryType)) { commonGeometryType = currentType; } else { commonGeometryType = Geometry.class; } } } // collect geometry for (int i = 0; i < g.getNumGeometries(); i++) { collectedGeometries.add(g.getGeometryN(i)); } } geoFind.reset(); } if (commonGeometryType != null && commonGeometryType.equals(Geometry.class)) { if (log != null && cell != null) { log.warn(new TransformationMessageImpl(cell, "Could not find common geometry type for aggregation", null)); } } if (commonGeometryType != null) { Geometry combined = combineGeometries(collectedGeometries, commonGeometryType); return new DefaultGeometryProperty<Geometry>(commonCrs, combined); } throw new NoResultException(); } @SuppressWarnings("unchecked") private static Geometry combineGeometries(List<? extends Geometry> collectedGeometries, Class<? extends Geometry> commonGeometryType) throws ClassCastException { GeometryFactory fact = new GeometryFactory(); if (Point.class.isAssignableFrom(commonGeometryType)) { return fact.createMultiPoint(((Collection<Point>) collectedGeometries) .toArray(new Point[collectedGeometries.size()])); } else if (LineString.class.isAssignableFrom(commonGeometryType)) { return CurveHelper.combineCurve((List<LineString>) collectedGeometries, fact, false); } else if (Polygon.class.isAssignableFrom(commonGeometryType)) { return fact.createMultiPolygon(((Collection<Polygon>) collectedGeometries) .toArray(new Polygon[collectedGeometries.size()])); } else { return fact.createGeometryCollection(collectedGeometries .toArray(new Geometry[collectedGeometries.size()])); } } /** * Determine the contained geometry type from a geometry. * * @param clazz the geometry class * @return the contained geometry typed */ private static Class<? extends Geometry> getContainedGeometryType( Class<? extends Geometry> clazz) { if (GeometryCollection.class.isAssignableFrom(clazz)) { if (MultiLineString.class.isAssignableFrom(clazz)) { return LineString.class; } if (MultiPoint.class.isAssignableFrom(clazz)) { return Point.class; } if (MultiPolygon.class.isAssignableFrom(clazz)) { return Polygon.class; } } else { if (LineString.class.isAssignableFrom(clazz)) { return LineString.class; } if (Point.class.isAssignableFrom(clazz)) { return Point.class; } if (Polygon.class.isAssignableFrom(clazz)) { return Polygon.class; } } return Geometry.class; } }