/*
* 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.extent;
import java.util.Arrays;
import java.util.Map;
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 eu.esdihumboldt.hale.common.align.model.ParameterValue;
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.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;
/**
* Computes the extent of all input geometries.
*
* @author Simon Templer
*/
public class ExtentTransformation extends
AbstractSingleTargetPropertyTransformation<TransformationEngine> implements ExtentFunction {
/**
* Number of Geometries to be processed at once by either extent option.
* Especially the "union" process run time depends on this value. A too
* large value causes a @code{java.lang.OutOfMemoryError}. The chosen value
* results in fairly good processing time of 30-60 seconds per 10,000
* geometries.
*/
private static final short SIMULTAN_PROCESS_GEOMS = 768;
@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 {
String paramType = getOptionalParameter(PARAM_TYPE, new ParameterValue(PARAM_BOUNDING_BOX))
.as(String.class);
Iterable<Object> geometries = Iterables.transform(variables.get(null),
new Function<PropertyValue, Object>() {
@Override
public Object apply(PropertyValue input) {
return input.getValue();
}
});
return calculateExtent(geometries, ExtentType.forId(paramType));
}
/**
* Calculate the extent of a set of geometries.
*
* @param geometries the geometries or instances containing geometries
* @param type the type of extent to calculate
* @return the calculated extent
* @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<?> calculateExtent(Iterable<?> geometries, ExtentType type)
throws TransformationException, NoResultException {
InstanceTraverser traverser = new DepthFirstInstanceTraverser(true);
GeometryFinder geoFind = new GeometryFinder(null);
GeometryFactory fact = new GeometryFactory();
CRSDefinition commonCrs = null;
Geometry[] geomsCollectingArray = new Geometry[SIMULTAN_PROCESS_GEOMS];
short geomsCollectedIdx = 0;
// int count = 0;
// System.err.println("Geoms Currently Processed;TotalMem;FreeMem");
for (Object value : geometries) {
traverser.traverse(value, geoFind);
// count += geoFind.getGeometries().size();
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();
// If geometry collecting array not filled.
if (geomsCollectedIdx < SIMULTAN_PROCESS_GEOMS - 1) {
geomsCollectingArray[geomsCollectedIdx++] = g;
}
// Geometry collecting array filled.
else {
geomsCollectingArray[geomsCollectedIdx] = g; // add last
// geometry
GeometryCollection gc = new GeometryCollection(geomsCollectingArray, fact);
geomsCollectingArray[0] = resolveParam(gc, type);
geomsCollectedIdx = 1;
}
}
geoFind.reset();
}
Geometry extent = resolveParam(
new GeometryCollection(Arrays.copyOfRange(geomsCollectingArray, 0,
geomsCollectedIdx), fact), type);
if (extent != null) {
return new DefaultGeometryProperty<Geometry>(commonCrs, extent);
}
throw new NoResultException();
}
/**
* Function resolves the extent option parameter and computes the specified
* extent type.
*
* @param gc GeometryCollection, the extent function is processed on
* @param type extent option parameter
* @return Geometry representing extent of input geometries
*/
private static Geometry resolveParam(GeometryCollection gc, ExtentType type) {
Geometry extent = null;
switch (type) {
case CONVEX_HULL:
// Compute convex hull.
extent = gc.convexHull();
break;
case UNION:
// Compute union.
// Alternative function <extent.union()> is slower and results in
// inconsistent topology errors within the jts-lib.
// lib-intern errors due to topology inconsistency errros.
extent = gc.buffer(0);
break;
case BBOX:
// Compute bounding box.
default:
// Ensure extent is not null. Default case is bounding box.
extent = gc.getEnvelope();
}
return extent;
}
}