/*
* Copyright (c) 2012 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:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.io.gml.geometry.handler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.xml.namespace.QName;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import eu.esdihumboldt.hale.common.core.io.IOProvider;
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.instance.model.Instance;
import eu.esdihumboldt.hale.common.schema.geometry.CRSDefinition;
import eu.esdihumboldt.hale.common.schema.geometry.GeometryProperty;
import eu.esdihumboldt.hale.common.schema.model.TypeConstraint;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.Binding;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.ElementType;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.GeometryType;
import eu.esdihumboldt.hale.io.gml.geometry.AbstractGeometryHandler;
import eu.esdihumboldt.hale.io.gml.geometry.FixedConstraintsGeometryHandler;
import eu.esdihumboldt.hale.io.gml.geometry.GMLGeometryUtil;
import eu.esdihumboldt.hale.io.gml.geometry.GeometryHandler;
import eu.esdihumboldt.hale.io.gml.geometry.GeometryNotSupportedException;
import eu.esdihumboldt.hale.io.gml.geometry.constraint.GeometryFactory;
/**
* Generic geometry handler for AbstractGeometryType.
*
* @author Simon Templer
*/
public class GenericGeometryHandler extends FixedConstraintsGeometryHandler {
/**
* Wraps a {@link CRSDefinition}.
*/
public static class CRSWrapper {
private final CRSDefinition crsDef;
/**
* Create a CRS wrapper
*
* @param crsDefinition the CRS definition
*/
public CRSWrapper(CRSDefinition crsDefinition) {
this.crsDef = crsDefinition;
}
/**
* Get the contained CRS definition.
*
* @return the CRS definition
*/
public CRSDefinition getCrsDef() {
return crsDef;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((crsDef == null) ? 0 : crsDef.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CRSWrapper other = (CRSWrapper) obj;
if (crsDef == null) {
if (other.crsDef != null)
return false;
}
else if (!crsDef.equals(other.crsDef))
return false;
return true;
}
}
private static final String ABSTRACT_GEOMETRY_TYPE = "AbstractGeometryType";
/**
* @see AbstractGeometryHandler#initSupportedTypes()
*/
@Override
protected Set<? extends QName> initSupportedTypes() {
Set<QName> types = new HashSet<QName>();
types.add(new QName(NS_GML, ABSTRACT_GEOMETRY_TYPE));
types.add(new QName(NS_GML_32, ABSTRACT_GEOMETRY_TYPE));
return types;
}
/**
* @see FixedConstraintsGeometryHandler#initConstraints()
*/
@Override
protected Collection<? extends TypeConstraint> initConstraints() {
Collection<TypeConstraint> constraints = new ArrayList<TypeConstraint>(3);
// binding is collection, as we can't be sure that all contained
// geometries share the same CRS
constraints.add(Binding.get(Collection.class));
// set element type binding to GeometryProperty
constraints.add(ElementType.get(GeometryProperty.class));
// geometry binding is Geometry, as we can't narrow it down further
constraints.add(GeometryType.get(Geometry.class));
// set geometry factory constraint
constraints.add(new GeometryFactory(this));
return constraints;
}
/**
* @see GeometryHandler#createGeometry(Instance, int, IOProvider)
*/
@Override
public Collection<GeometryProperty<?>> createGeometry(Instance instance, int srsDimension,
IOProvider reader) throws GeometryNotSupportedException {
CRSDefinition defaultCrsDef = GMLGeometryUtil.findCRS(instance);
// depth first traverser that on cancel continues traversal but w/o the
// children of the current object
InstanceTraverser traverser = new DepthFirstInstanceTraverser(true);
GeometryFinder geoFind = new GeometryFinder(defaultCrsDef);
traverser.traverse(instance, geoFind);
return createGeometry(instance, geoFind.getGeometries(), defaultCrsDef, reader);
}
@SuppressWarnings("unchecked")
static Class<? extends Geometry> findClosestCommonSuperclass(Class<? extends Geometry> a,
Class<? extends Geometry> b) {
while (!a.isAssignableFrom(b)) {
a = (Class<? extends Geometry>) a.getSuperclass();
}
return a;
}
/**
* Create a geometry value from a given instance.
*
* @param instance the instance
* @param childGeometries the child geometries found in the instance
* @param defaultCrs the definition of the default CRS for this instance
* @param reader the IO provider
* @return the geometry value derived from the instance, the return type
* should match the {@link Binding} created in
* {@link #getTypeConstraints(TypeDefinition)}.
* @throws GeometryNotSupportedException if the type definition doesn't
* represent a geometry type supported by the handler
*/
@SuppressWarnings("unused")
protected Collection<GeometryProperty<?>> createGeometry(Instance instance,
List<GeometryProperty<?>> childGeometries, CRSDefinition defaultCrs, IOProvider reader)
throws GeometryNotSupportedException {
List<Geometry> geomList = new ArrayList<Geometry>();
Class<? extends Geometry> commonGeomType = null;
CRSWrapper commonCrs = null;
boolean allowCombine = true;
// collect geometries and check for common geometry type and CRS
// TODO partition based on CRS?
for (GeometryProperty<?> geomProp : childGeometries) {
if (geomProp.getGeometry() instanceof GeometryCollection) {
GeometryCollection geomCollection = (GeometryCollection) geomProp.getGeometry();
for (int i = 0; i < geomCollection.getNumGeometries(); i++) {
// find the common geometry class
Class<? extends Geometry> geometryType = geomCollection.getGeometryN(i)
.getClass();
if (commonGeomType == null) {
commonGeomType = geometryType;
}
else if (!commonGeomType.equals(geometryType)) {
// TODO determine common type in inheritance?
commonGeomType = Geometry.class;
}
geomList.add(geomCollection.getGeometryN(i));
}
}
else {
// find the common geometry class
Class<? extends Geometry> geometryType = geomProp.getGeometry().getClass();
if (commonGeomType == null) {
commonGeomType = geometryType;
}
else if (!commonGeomType.equals(geometryType)) {
// find common super class
commonGeomType = findClosestCommonSuperclass(commonGeomType, geometryType);
}
geomList.add(geomProp.getGeometry());
}
// check common CRS
CRSWrapper crs = new CRSWrapper(geomProp.getCRSDefinition());
if (commonCrs == null) {
commonCrs = crs;
}
else if (!commonCrs.equals(crs)) {
allowCombine = false;
}
}
if (allowCombine && commonGeomType != null) {
if (!(commonGeomType.equals(Polygon.class) || commonGeomType.equals(Point.class)
|| commonGeomType.equals(LineString.class))) {
// if we don't have a supported common geometry type
// check if it is a subclass of a supported type
if (LineString.class.isAssignableFrom(commonGeomType)) {
// for instance for InterpolatedLineString
commonGeomType = LineString.class;
}
if (Point.class.isAssignableFrom(commonGeomType)) {
commonGeomType = Point.class;
}
if (Polygon.class.isAssignableFrom(commonGeomType)) {
commonGeomType = Polygon.class;
}
}
Geometry geom = null;
if (commonGeomType.equals(Polygon.class)) {
// create a MultiPolygon
Polygon[] polygons = new Polygon[geomList.size()];
for (int i = 0; i < geomList.size(); i++) {
polygons[i] = (Polygon) geomList.get(i);
}
geom = combine(polygons, reader);
}
else if (commonGeomType.equals(LineString.class)) {
// create a MultiLineString
LineString[] lines = new LineString[geomList.size()];
for (int i = 0; i < geomList.size(); i++) {
lines[i] = (LineString) geomList.get(i);
}
geom = combine(lines, reader);
}
else if (commonGeomType.equals(Point.class)) {
// create a MultiPoint
Point[] points = new Point[geomList.size()];
for (int i = 0; i < geomList.size(); i++) {
points[i] = (Point) geomList.get(i);
}
geom = combine(points, reader);
}
if (geom != null) {
// returned combined property
CRSDefinition crs = (commonCrs != null && commonCrs.getCrsDef() != null)
? (commonCrs.getCrsDef()) : (defaultCrs);
return Collections.<GeometryProperty<?>> singleton(
new DefaultGeometryProperty<Geometry>(crs, geom));
}
}
// fall-back: return a collection of geometry properties
if (childGeometries.isEmpty()) {
return null;
}
return childGeometries;
}
/**
* Combine points to a geometry.
*
* @param points the points to combine
* @param reader the reader of the related XML file
* @return the combined geometry
*/
protected Geometry combine(Point[] points, IOProvider reader) {
return getGeometryFactory().createMultiPoint(points);
}
/**
* Combine lines to a geometry.
*
* @param lines the lines to combine
* @param reader the reader of the related XML file
* @return the combined geometry
*/
protected Geometry combine(LineString[] lines, IOProvider reader) {
return getGeometryFactory().createMultiLineString(lines);
}
/**
* Combine polygons to a geometry.
*
* @param polygons the polygons to combine
* @param reader the reader of the related XML file
* @return the combined geometry
*/
protected Geometry combine(Polygon[] polygons, IOProvider reader) {
return getGeometryFactory().createMultiPolygon(polygons);
}
}