/* * Copyright (c) 2014 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.common.instance.io.impl; import java.util.Collection; import org.geotools.geometry.jts.JTS; import org.geotools.referencing.CRS; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import com.vividsolutions.jts.geom.Geometry; import eu.esdihumboldt.hale.common.core.io.Value; import eu.esdihumboldt.hale.common.core.io.report.IOReporter; import eu.esdihumboldt.hale.common.core.io.report.impl.IOMessageImpl; import eu.esdihumboldt.hale.common.instance.geometry.CRSDefinitionManager; import eu.esdihumboldt.hale.common.instance.geometry.CRSDefinitionUtil; import eu.esdihumboldt.hale.common.instance.geometry.impl.CodeDefinition; import eu.esdihumboldt.hale.common.instance.io.GeoInstanceWriter; import eu.esdihumboldt.hale.common.instance.io.util.EnumWindingOrderTypes; import eu.esdihumboldt.hale.common.schema.geometry.CRSDefinition; import eu.esdihumboldt.hale.common.schema.geometry.GeometryProperty; import eu.esdihumboldt.util.Pair; import eu.esdihumboldt.util.geometry.WindingOrder; /** * Abstract {@link GeoInstanceWriter} base implementation * * @author Simon Templer * @since 2.9 */ public abstract class AbstractGeoInstanceWriter extends AbstractInstanceWriter implements GeoInstanceWriter { @Override public void setTargetCRS(CRSDefinition crs) { setParameter(PARAM_TARGET_CRS, Value.of(CRSDefinitionManager.getInstance().asString(crs))); } @Override public CRSDefinition getTargetCRS() { return CRSDefinitionManager.getInstance() .parse(getParameter(PARAM_TARGET_CRS).as(String.class)); } @Override public void setCustomEPSGPrefix(String epsgPrefix) { setParameter(PARAM_CRS_CODE_FORMAT, Value.of(epsgPrefix)); } @Override public String getCustomEPSGPrefix() { return getParameter(PARAM_CRS_CODE_FORMAT).as(String.class); } @Override public void setWindingOrder(EnumWindingOrderTypes windingOrderType) { setParameter(PARAM_UNIFY_WINDING_ORDER, Value.of(windingOrderType.toString())); } @Override public EnumWindingOrderTypes getWindingOrder() { EnumWindingOrderTypes value = getParameter(PARAM_UNIFY_WINDING_ORDER) .as(EnumWindingOrderTypes.class); if (value == null) return getDefaultWindingOrder(); else return value; } /** * Get default Winding Order. Function is to give functionality to the * subType to change the default Winding order. * * @return EnumWindingOrderTypes default Winding order */ protected EnumWindingOrderTypes getDefaultWindingOrder() { return EnumWindingOrderTypes.noChanges; } /** * Convert the given geometry to the target CRS, if possible (and a target * CRS is set). * * @param geom the geometry to convert * @param sourceCrs the source CRS * @param report the reporter * @return a pair of geometry and CRS definition, either the converted * geometry and the target CRS or the given geometry and the source * CRS */ protected Pair<Geometry, CRSDefinition> convertGeometry(Geometry geom, CRSDefinition sourceCrs, IOReporter report) { if (sourceCrs != null && sourceCrs.getCRS() != null && getTargetCRS() != null && getTargetCRS().getCRS() != null) { try { // TODO cache mathtransforms? MathTransform transform = CRS.findMathTransform(sourceCrs.getCRS(), getTargetCRS().getCRS()); Geometry targetGeometry = JTS.transform(geom, transform); return new Pair<>(targetGeometry, getTargetCRS()); } catch (Exception e) { if (report != null) { report.error(new IOMessageImpl("Could not convert geometry to target CRS", e)); } // return original geometry return new Pair<>(geom, sourceCrs); } } else { // return original geometry return new Pair<>(geom, sourceCrs); } } /** * Returns a pair of geometry and associated CRS definition for the given * value. The value has to be a Geometry or a GeometryProperty, otherwise * <code>null</code> is returned. * * @param value the value to extract the information from * @param allowConvert if conversion to the target CRS should be performed * if applicable * @param report the reporter * @return a pair of geometry and CRS definition (latter may be * <code>null</code>), or <code>null</code> if the argument doesn't * contain a geometry */ protected Pair<Geometry, CRSDefinition> extractGeometry(Object value, boolean allowConvert, IOReporter report) { Pair<Geometry, CRSDefinition> pair = getGeometryPair(value, allowConvert, report); if (pair == null) return null; return unifyGeometryPair(pair, report); } /** * Returns a pair of geometry and associated CRS definition for the given * value. The value has to be a Geometry or a GeometryProperty, otherwise * <code>null</code> is returned. * * @param value the value to extract the information from * @param allowConvert if conversion to the target CRS should be performed * if applicable * @param report the reporter * @return a pair of geometry and CRS definition (latter may be * <code>null</code>), or <code>null</code> if the argument doesn't * contain a geometry */ private Pair<Geometry, CRSDefinition> getGeometryPair(Object value, boolean allowConvert, IOReporter report) { // TODO collection handling (-> happens for example with target // CompositeSurface) if (value instanceof Collection) { if (!((Collection<?>) value).isEmpty()) { // TODO combine geometries? value = ((Collection<?>) value).iterator().next(); } } if (value instanceof Geometry) { return new Pair<>((Geometry) value, null); } else if (value instanceof GeometryProperty<?>) { CRSDefinition def = ((GeometryProperty<?>) value).getCRSDefinition(); Geometry geom = ((GeometryProperty<?>) value).getGeometry(); if (allowConvert) { return convertGeometry(geom, def, report); } return new Pair<>(geom, def); } else return null; } /** * Returns a pair of unified geometry of given geometry and associated CRS * definition based on Winding order supplied. * * @param pair A pair of Geometry and CRSDefinition, on which winding * process will get done. * @param report the reporter * @return Unified Pair . */ protected Pair<Geometry, CRSDefinition> unifyGeometryPair(Pair<Geometry, CRSDefinition> pair, IOReporter report) { // get Geometry object Geometry geom = pair.getFirst(); if (geom == null) { return pair; } // getting CRS CRSDefinition def = pair.getSecond(); CoordinateReferenceSystem crs = null; if (def != null) crs = pair.getSecond().getCRS(); // unify geometry geom = unifyGeometry(geom, report, crs); return new Pair<>(geom, pair.getSecond()); } /** * Returns a unified geometry of given geometry based on Winding order * supplied. * * @param geom The Geometry object, on which winding process will get done. * @param report the reporter * @param crs Coordinate Reference System * @return Unified geometry . */ protected Geometry unifyGeometry(Geometry geom, IOReporter report, CoordinateReferenceSystem crs) { if (geom == null) { return geom; } // getting winding order EnumWindingOrderTypes windingOrder = getWindingOrder(); if (windingOrder == null || windingOrder == EnumWindingOrderTypes.noChanges) { return geom; } else { Geometry unifiedGeometry; // unify geometry using WindingOrder utility. switch (windingOrder) { case counterClockwise: unifiedGeometry = WindingOrder.unifyWindingOrder(geom, true, crs); break; case clockwise: unifiedGeometry = WindingOrder.unifyWindingOrder(geom, false, crs); break; default: if (report != null) { report.error(new IOMessageImpl( "Parameter encountered as winding order is not known: " + windingOrder.toString(), null)); } unifiedGeometry = geom; break; } return unifiedGeometry; } } /** * Extract a CRS code from the given CRS definition. * * @param crsDef the CRS definition * @return the CRS code, may be <code>null</code> */ protected String extractCode(CRSDefinition crsDef) { if (crsDef == null) { return null; } String orgCode = CRSDefinitionUtil.getCode(crsDef); String customPrefix = getCustomEPSGPrefix(); if (orgCode != null && customPrefix != null) { // try to extract EPSG code String epsgCode = CodeDefinition.extractEPSGCode(orgCode); if (epsgCode != null) { return customPrefix + epsgCode; } } return orgCode; } }