/* * 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.writer.internal.geometry.writers; import java.text.DecimalFormat; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import de.fhg.igd.slf4jplus.ALogger; import de.fhg.igd.slf4jplus.ALoggerFactory; import eu.esdihumboldt.hale.common.schema.model.DefinitionUtil; import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition; import eu.esdihumboldt.hale.common.schema.model.TypeDefinition; import eu.esdihumboldt.hale.io.gml.writer.internal.geometry.Descent; import eu.esdihumboldt.hale.io.gml.writer.internal.geometry.GeometryWriter; import eu.esdihumboldt.util.geometry.NumberFormatter; /** * Abstract geometry writer implementation * * @author Simon Templer * @partner 01 / Fraunhofer Institute for Computer Graphics Research * @version $Id$ * @param <T> the geometry type */ public abstract class AbstractGeometryWriter<T extends Geometry> extends AbstractPathMatcher implements GeometryWriter<T> { private static final ALogger log = ALoggerFactory.getLogger(AbstractGeometryWriter.class); private final Class<T> geometryType; private final Set<QName> compatibleTypes = new HashSet<QName>(); /** * The attribute type names supported for writing coordinates with * {@link #writeCoordinates(XMLStreamWriter, Coordinate[], TypeDefinition, String, DecimalFormat)} * or * {@link #descendAndWriteCoordinates(XMLStreamWriter, Pattern, Coordinate[], TypeDefinition, QName, String, boolean, DecimalFormat)} * . * * Use for validating end-points. */ private final static Set<String> SUPPORTED_COORDINATES_TYPES = Collections .unmodifiableSet(new HashSet<String>(Arrays.asList("DirectPositionType", //$NON-NLS-1$ "DirectPositionListType", "CoordinatesType"))); //$NON-NLS-1$ //$NON-NLS-2$ /** * Constructor * * @param geometryType the geometry type */ public AbstractGeometryWriter(Class<T> geometryType) { super(); this.geometryType = geometryType; } /** * @see GeometryWriter#getCompatibleTypes() */ @Override public Set<QName> getCompatibleTypes() { return Collections.unmodifiableSet(compatibleTypes); } /** * Add a compatible type. A {@link Pattern#GML_NAMESPACE_PLACEHOLDER} * namespace references the GML namespace. * * @param typeName the type name */ public void addCompatibleType(QName typeName) { compatibleTypes.add(typeName); } /** * @see GeometryWriter#getGeometryType() */ @Override public Class<T> getGeometryType() { return geometryType; } /** * Verify the verification end point. After reaching the end-point of a * verification pattern this method is called with the * {@link TypeDefinition} of the end-point to assure the needed structure is * present (e.g. a DirectPositionListType element). If no verification * pattern is present the end-point of the matched base pattern will be * verified. The default implementation checks for properties with any of * the types supported for writing coordinates. * * @see #SUPPORTED_COORDINATES_TYPES * * @param endPoint the end-point type definition * * @return if the end-point is valid for writing the geometry */ @Override protected boolean verifyEndPoint(TypeDefinition endPoint) { for (PropertyDefinition attribute : DefinitionUtil.getAllProperties(endPoint)) { // XXX is this enough? or must groups be handled explicitly? if (SUPPORTED_COORDINATES_TYPES .contains(attribute.asProperty().getPropertyType().getName().getLocalPart())) { // a valid property was found return true; } } return false; } /** * Write coordinates into a posList or coordinates property * * @param writer the XML stream writer * @param descendPattern the pattern to descend * @param coordinates the coordinates to write * @param elementType the type of the encompassing element * @param elementName the encompassing element name * @param gmlNs the GML namespace * @param unique if the path's start element cannot be repeated * @param decimalFormatter a decimal formatter to format geometry * coordinates * @throws XMLStreamException if an error occurs writing the coordinates */ protected static void descendAndWriteCoordinates(XMLStreamWriter writer, Pattern descendPattern, Coordinate[] coordinates, TypeDefinition elementType, QName elementName, String gmlNs, boolean unique, DecimalFormat decimalFormatter) throws XMLStreamException { Descent descent = descend(writer, descendPattern, elementType, elementName, gmlNs, unique); // write geometry writeCoordinates(writer, coordinates, descent.getPath().getLastType(), gmlNs, decimalFormatter); descent.close(); } /** * Write coordinates into a pos, posList or coordinates property * * @param writer the XML stream writer * @param coordinates the coordinates to write * @param elementType the type of the encompassing element * @param gmlNs the GML namespace * @param decimalFormatter a decimal formatter to format geometry * coordinates * @throws XMLStreamException if an error occurs writing the coordinates */ protected static void writeCoordinates(XMLStreamWriter writer, Coordinate[] coordinates, TypeDefinition elementType, String gmlNs, DecimalFormat decimalFormatter) throws XMLStreamException { if (coordinates.length > 1) { if (writeList(writer, coordinates, elementType, gmlNs, decimalFormatter)) { return; } } if (writePos(writer, coordinates, elementType, gmlNs, null, decimalFormatter)) { return; } if (coordinates.length <= 1) { if (writeList(writer, coordinates, elementType, gmlNs, decimalFormatter)) { return; } } log.error("Unable to write coordinates to element of type " + //$NON-NLS-1$ elementType.getDisplayName()); } /** * Write coordinates into a pos property * * @param writer the XML stream writer * @param coordinates the coordinates to write * @param elementType the type of the encompassing element * @param gmlNs the GML namespace * @param posName the name of the desired DirectPositionType property, or * <code>null</code> if any * @param decimalFormatter a decimal formatter to format geometry * coordinates * @return if writing the coordinates was successful * @throws XMLStreamException if an error occurs writing the coordinates */ protected static boolean writePos(XMLStreamWriter writer, Coordinate[] coordinates, TypeDefinition elementType, String gmlNs, String posName, DecimalFormat decimalFormatter) throws XMLStreamException { PropertyDefinition posAttribute = null; // check for DirectPositionType // XXX is this enough? or must groups be handled? for (PropertyDefinition att : DefinitionUtil.getAllProperties(elementType)) { if (att.getPropertyType().getName().equals(new QName(gmlNs, "DirectPositionType"))) { //$NON-NLS-1$ // found a property with DirectPositionType if (posName == null || posName.equals(att.getName().getLocalPart())) { posAttribute = att; break; } } } // TODO support for CoordType if (posAttribute != null) { // TODO possibly write repeated positions writer.writeStartElement(posAttribute.getName().getNamespaceURI(), posAttribute.getName().getLocalPart()); // write coordinates separated by spaces if (coordinates.length > 0) { Coordinate coordinate = coordinates[0]; writer.writeCharacters(NumberFormatter.formatTo(coordinate.x, decimalFormatter)); writer.writeCharacters(" "); //$NON-NLS-1$ writer.writeCharacters(NumberFormatter.formatTo(coordinate.y, decimalFormatter)); if (!Double.isNaN(coordinate.z)) { writer.writeCharacters(" "); //$NON-NLS-1$ writer.writeCharacters( NumberFormatter.formatTo(coordinate.z, decimalFormatter)); } } writer.writeEndElement(); return true; } else { return false; } } /** * Write coordinates into a posList or coordinates property * * @param writer the XML stream writer * @param coordinates the coordinates to write * @param elementType the type of the encompassing element * @param gmlNs the GML namespace * @param decimalFormatter a decimal formatter to format geometry * coordinates * @return if writing the coordinates was successful * @throws XMLStreamException if an error occurs writing the coordinates */ private static boolean writeList(XMLStreamWriter writer, Coordinate[] coordinates, TypeDefinition elementType, String gmlNs, DecimalFormat decimalFormatter) throws XMLStreamException { PropertyDefinition listAttribute = null; String delimiter = " "; //$NON-NLS-1$ String setDelimiter = " "; //$NON-NLS-1$ // check for DirectPositionListType for (PropertyDefinition att : DefinitionUtil.getAllProperties(elementType)) { // XXX is this enough? or must groups be handled explicitly? if (att.getPropertyType().getName() .equals(new QName(gmlNs, "DirectPositionListType"))) { //$NON-NLS-1$ listAttribute = att; break; } } if (listAttribute == null) { // check for CoordinatesType for (PropertyDefinition att : DefinitionUtil.getAllProperties(elementType)) { if (att.getPropertyType().getName().equals(new QName(gmlNs, "CoordinatesType"))) { //$NON-NLS-1$ listAttribute = att; delimiter = ","; //$NON-NLS-1$ break; } } } if (listAttribute != null) { writer.writeStartElement(listAttribute.getName().getNamespaceURI(), listAttribute.getName().getLocalPart()); boolean first = true; // write coordinates separated by spaces for (Coordinate coordinate : coordinates) { if (first) { first = false; } else { writer.writeCharacters(setDelimiter); } writer.writeCharacters(NumberFormatter.formatTo(coordinate.x, decimalFormatter)); writer.writeCharacters(delimiter); writer.writeCharacters(NumberFormatter.formatTo(coordinate.y, decimalFormatter)); if (!Double.isNaN(coordinate.z)) { writer.writeCharacters(delimiter); writer.writeCharacters( NumberFormatter.formatTo(coordinate.z, decimalFormatter)); } } writer.writeEndElement(); return true; } else { return false; } } @SuppressWarnings("unchecked") @Override public boolean accepts(Geometry geometry) { return geometryType.isInstance(geometry) && checkValid((T) geometry); } /** * Check if the given geometry is valid to be written by this writer. * * @param geometry the geometry to check * @return if the geometry is valid, the default implementation just returns * <code>true</code> */ protected boolean checkValid(T geometry) { // override me return true; } }