/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.gml2.bindings; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.eclipse.xsd.XSDComplexTypeDefinition; import org.eclipse.xsd.XSDCompositor; import org.eclipse.xsd.XSDDerivationMethod; import org.eclipse.xsd.XSDElementDeclaration; import org.eclipse.xsd.XSDFactory; import org.eclipse.xsd.XSDModelGroup; import org.eclipse.xsd.XSDParticle; import org.eclipse.xsd.XSDTypeDefinition; import org.eclipse.xsd.util.XSDConstants; import org.geotools.feature.NameImpl; import org.geotools.gml2.GML; import org.geotools.gml2.GMLConfiguration; import org.geotools.metadata.iso.citation.Citations; import org.geotools.util.logging.Logging; import org.geotools.xlink.XLINK; import org.geotools.xml.Configuration; import org.geotools.xml.Encoder; import org.geotools.xml.SchemaIndex; import org.geotools.xml.Schemas; import org.geotools.xs.XS; import org.opengis.feature.ComplexAttribute; import org.opengis.feature.Feature; import org.opengis.feature.Property; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.ComplexType; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.feature.type.Name; import org.opengis.geometry.BoundingBox; import org.opengis.metadata.Identifier; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.crs.ProjectedCRS; import org.opengis.referencing.cs.AxisDirection; import org.opengis.referencing.cs.CoordinateSystem; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; import org.xml.sax.Attributes; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; 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; /** * Utility methods used by gml2 bindigns when encodding. * * @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org * @author Ben Caradoc-Davies, CSIRO Exploration and Mining * * @source $URL$ */ public class GML2EncodingUtils { /** logging instance */ static Logger LOGGER = Logging.getLogger( "org.geotools.gml"); static final int LON_LAT = 0; static final int LAT_LON = 1; static final int INAPPLICABLE = 2; public static String epsgCode(CoordinateReferenceSystem crs) { if (crs == null) { return null; } for (Iterator i = crs.getIdentifiers().iterator(); i.hasNext();) { Identifier id = (Identifier) i.next(); //return "EPSG:" + id.getCode(); if ((id.getAuthority() != null) && id.getAuthority().getTitle().equals(Citations.EPSG.getTitle())) { return id.getCode(); } } return null; } /** * @deprecated use {@link #toURI(CoordinateReferenceSystem)}. */ public static String crs(CoordinateReferenceSystem crs) { return toURI(crs); } /** * Encodes the crs object as a uri. */ public static String toURI(CoordinateReferenceSystem crs) { return toURI(crs,false); } /** * Encodes the crs object as a uri. * <p> * The axis order of the crs determines which form of uri is used. * </p> */ public static String toURI(CoordinateReferenceSystem crs, boolean forceOldStyle) { String code = epsgCode(crs); int axisOrder = axisOrder(crs); if (code != null) { if (forceOldStyle ||( (axisOrder == LON_LAT) || (axisOrder == INAPPLICABLE)) ) { return "http://www.opengis.net/gml/srs/epsg.xml#" + code; } else { //return "urn:x-ogc:def:crs:EPSG:6.11.2:" + code; return "urn:x-ogc:def:crs:EPSG:" + code; } } return null; } /** * Returns the axis order of the provided {@link CoordinateReferenceSystem} object. * @param crs * @return <ul> * <li>LON_LAT if the axis order is longitude/latitude</li> * <li>LAT_LON if the axis order is latitude/longitude</li> * <li>INAPPLICABLE if the CRS does not deal with longitude/latitude * (such as vertical or engineering CRS)</li> */ static int axisOrder(CoordinateReferenceSystem crs) { CoordinateSystem cs = null; if (crs instanceof ProjectedCRS) { ProjectedCRS pcrs = (ProjectedCRS) crs; cs = pcrs.getBaseCRS().getCoordinateSystem(); } else if (crs instanceof GeographicCRS) { cs = crs.getCoordinateSystem(); } else { return INAPPLICABLE; } int dimension = cs.getDimension(); int longitudeDim = -1; int latitudeDim = -1; for (int i = 0; i < dimension; i++) { AxisDirection dir = cs.getAxis(i).getDirection().absolute(); if (dir.equals(AxisDirection.EAST)) { longitudeDim = i; } if (dir.equals(AxisDirection.NORTH)) { latitudeDim = i; } } if ((longitudeDim >= 0) && (latitudeDim >= 0)) { if (longitudeDim < latitudeDim) { return LON_LAT; } else { return LAT_LON; } } return INAPPLICABLE; } /** * Determines the crs of the geometry by checking {@link Geometry#getUserData()}. * <p> * This method returns <code>null</code> when no crs can be found. * </p> */ public static CoordinateReferenceSystem getCRS(Geometry g) { if (g.getUserData() == null) { return null; } if (g.getUserData() instanceof CoordinateReferenceSystem) { return (CoordinateReferenceSystem) g.getUserData(); } if (g.getUserData() instanceof Map) { Map userData = (Map) g.getUserData(); return (CoordinateReferenceSystem) userData.get(CoordinateReferenceSystem.class); } return null; } /** * Determines the identifier (gml:id) of the geometry by checking * {@link Geometry#getUserData()}. * <p> * This method returns <code>null</code> when no id can be found. * </p> */ public static String getID(Geometry g) { return getMetadata( g, "gml:id" ); } /** * Determines the description (gml:description) of the geometry by checking * {@link Geometry#getUserData()}. * <p> * This method returns <code>null</code> when no name can be found. * </p> */ public static String getName(Geometry g) { return getMetadata( g, "gml:name" ); } /** * Determines the name (gml:name) of the geometry by checking * {@link Geometry#getUserData()}. * <p> * This method returns <code>null</code> when no description can be found. * </p> */ public static String getDescription(Geometry g) { return getMetadata( g, "gml:description" ); } static String getMetadata(Geometry g, String metadata) { if (g.getUserData() instanceof Map) { Map userData = (Map) g.getUserData(); return (String) userData.get(metadata); } return null; } public static Element AbstractFeatureType_encode(Object object, Document document, Element value) { Feature feature = (Feature) object; FeatureType featureType = feature.getType(); String namespace = featureType.getName().getNamespaceURI(); String typeName = featureType.getName().getLocalPart(); Element encoding = document.createElementNS(namespace, typeName); encoding.setAttributeNS(null, "fid", feature.getIdentifier().getID()); return encoding; } /** * Return gml:boundedBy property if wanted. * * @param feature * feature for which bounds might be required * @param configuration * encoder configuration, used to suppress feature bounds * @return the feature bounds, or null if none or unwanted */ private static BoundingBox getBoundedBy(Feature feature, Configuration configuration) { // check for flag not to include bounds if (configuration.hasProperty(GMLConfiguration.NO_FEATURE_BOUNDS)) { return null; } else { BoundingBox bounds = feature.getBounds(); // do a check for the case where the feature has no geometry properties if (bounds.isEmpty() && (feature.getDefaultGeometryProperty() == null || feature .getDefaultGeometryProperty().getValue() == null)) { return null; } else { return bounds; } } } public static List AbstractFeatureType_getProperties(Object object, XSDElementDeclaration element, SchemaIndex schemaIndex, Set<String> toFilter, Configuration configuration) { Feature feature = (Feature) object; //check if this was a resolved feature, if so dont return anything // TODO: this is just a hack for our lame xlink implementation if (feature.getUserData().get("xlink:id") != null) { return Collections.EMPTY_LIST; } FeatureType featureType = feature.getType(); String namespace = featureType.getName().getNamespaceURI(); if (namespace == null) { namespace = element.getTargetNamespace(); } String typeName = featureType.getName().getLocalPart(); QName qualifiedTypeName = new QName(namespace, typeName); //find the type in the schema XSDTypeDefinition type = schemaIndex.getTypeDefinition(qualifiedTypeName); if (type == null) { //type not found, do a check for an element, and use its type XSDElementDeclaration e = schemaIndex.getElementDeclaration(qualifiedTypeName); if (e != null) { type = e.getTypeDefinition(); } } if (type == null) { if (featureType instanceof SimpleFeatureType) { // could not find the feature type in the schema, create a mock one LOGGER.warning("Could find type for " + typeName + " in the schema, generating type from feature."); type = createXmlTypeFromFeatureType((SimpleFeatureType) featureType, schemaIndex, toFilter); } else { // look for an element declaration smuggled in the UserData map. XSDElementDeclaration e = (XSDElementDeclaration) feature.getDescriptor() .getUserData().get(XSDElementDeclaration.class); if (e != null) { type = e.getTypeDefinition(); } else { throw new RuntimeException("Could not find type for " + qualifiedTypeName + " in schema"); } } } List particles = Schemas.getChildElementParticles(type, true); List properties = new ArrayList(); O: for (Iterator p = particles.iterator(); p.hasNext();) { XSDParticle particle = (XSDParticle) p.next(); XSDElementDeclaration attribute = (XSDElementDeclaration) particle.getContent(); if (attribute.isElementDeclarationReference()) { attribute = attribute.getResolvedElementDeclaration(); } if (GML.boundedBy .equals(new QName(attribute.getTargetNamespace(), attribute.getName()))) { BoundingBox bounds = getBoundedBy(feature, configuration); if (bounds != null) { properties.add(new Object[] { particle, bounds }); } } else if (featureType instanceof SimpleFeatureType) { // simple feature brain damage: discard namespace // make sure the feature type has an element if (!isValidDescriptor(featureType, new NameImpl(attribute.getName()))) { continue; } // get the value Object attributeValue = ((SimpleFeature) feature).getAttribute(attribute.getName()); properties.add(new Object[] { particle, attributeValue }); } else { // namespaces matter for non-simple feature types Name propertyName = new NameImpl(attribute.getTargetNamespace(), attribute .getName()); // make sure the feature type has an element if (!isValidDescriptor(featureType, propertyName)) { continue; } // get the value (might be multiple) for (Property property : feature.getProperties(propertyName)) { Object value; if (property instanceof ComplexAttribute) { // do not unpack complex attributes as these may have their own bindings, which // will be applied by the encoder value = property; } else { // non-complex bindings are unpacked as for simple feature case value = property.getValue(); } properties.add(new Object[] { particle, value }); } } } return properties; } public static XSDTypeDefinition createXmlTypeFromFeatureType(SimpleFeatureType featureType, SchemaIndex schemaIndex, Set<String> toFilter ) { XSDFactory f = XSDFactory.eINSTANCE; Document dom; try { dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); } catch (ParserConfigurationException e) { throw new RuntimeException( e ); } XSDComplexTypeDefinition type = f.createXSDComplexTypeDefinition(); type.setTargetNamespace( featureType.getName().getNamespaceURI() ); type.setName( featureType.getTypeName() + "Type" ); type.setDerivationMethod(XSDDerivationMethod.EXTENSION_LITERAL); type.setBaseTypeDefinition(schemaIndex.getTypeDefinition( GML.AbstractFeatureType ) ); XSDModelGroup group = f.createXSDModelGroup(); group.setCompositor(XSDCompositor.SEQUENCE_LITERAL); List attributes = featureType.getAttributeDescriptors(); for (int i = 0; i < attributes.size(); i++) { AttributeDescriptor attribute = (AttributeDescriptor) attributes.get(i); if ( toFilter.contains( attribute.getLocalName() ) ) { continue; } XSDElementDeclaration element = f.createXSDElementDeclaration(); element.setName(attribute.getLocalName()); element.setNillable(attribute.isNillable()); //check for geometry if ( attribute instanceof GeometryDescriptor ) { Class binding = attribute.getType().getBinding(); if ( Point.class.isAssignableFrom( binding ) ) { element.setTypeDefinition( schemaIndex.getTypeDefinition(GML.PointPropertyType)); } else if ( LineString.class.isAssignableFrom( binding ) ) { element.setTypeDefinition( schemaIndex.getTypeDefinition(GML.LineStringPropertyType)); } else if ( Polygon.class.isAssignableFrom( binding) ) { element.setTypeDefinition( schemaIndex.getTypeDefinition(GML.PolygonPropertyType)); } else if ( MultiPoint.class.isAssignableFrom( binding ) ) { element.setTypeDefinition( schemaIndex.getTypeDefinition(GML.MultiPointPropertyType)); } else if ( MultiLineString.class.isAssignableFrom( binding ) ) { element.setTypeDefinition( schemaIndex.getTypeDefinition(GML.MultiLineStringPropertyType)); } else if ( MultiPolygon.class.isAssignableFrom( binding) ) { element.setTypeDefinition( schemaIndex.getTypeDefinition(GML.MultiPolygonPropertyType)); } else { element.setTypeDefinition( schemaIndex.getTypeDefinition(GML.GeometryPropertyType)); } } else { //TODO: do a proper mapping element.setTypeDefinition(schemaIndex.getTypeDefinition(XS.STRING)); } XSDParticle particle = f.createXSDParticle(); particle.setMinOccurs(attribute.getMinOccurs()); particle.setMaxOccurs(attribute.getMaxOccurs()); particle.setContent(element); particle.setElement( dom.createElementNS( XSDConstants.SCHEMA_FOR_SCHEMA_URI_2001, "element" ) ); group.getContents().add(particle); } XSDParticle particle = f.createXSDParticle(); particle.setContent(group); particle.setElement( dom.createElementNS( XSDConstants.SCHEMA_FOR_SCHEMA_URI_2001, "sequence") ); type.setContent(particle); return type; } public static Object GeometryPropertyType_getProperty(Geometry geometry, QName name) { return GeometryPropertyType_getProperty(geometry,name,true); } public static Object GeometryPropertyType_getProperty(Geometry geometry, QName name, boolean includeAbstractGeometry ) { if (GML.Point.equals( name ) || GML.LineString.equals( name ) || GML.Polygon.equals( name ) || GML.MultiPoint.equals( name ) || GML.MultiLineString.equals( name ) || GML.MultiPolygon.equals( name ) || (includeAbstractGeometry && GML._Geometry.equals(name) )) { //if the geometry is null, return null if ( isEmpty( geometry ) ) { return null; } return geometry; } if (XLINK.HREF.equals(name)) { //only process if geometry is empty if ( isEmpty(geometry) ) { String id = getID( geometry ); if ( id != null ) { return "#" + id; } } } return null; } public static List GeometryPropertyType_getProperties(Geometry geometry) { String id = getID( geometry ); if ( !isEmpty(geometry) && id != null ) { // return a comment which is hte xlink href return Collections.singletonList(new Object[] { Encoder.COMMENT, "#" +id }); } return null; } static boolean isEmpty( Geometry geometry ) { if ( geometry.isEmpty() ) { //check for case of multi geometry, if it has > 0 goemetries // we consider this to be not empty if ( geometry instanceof GeometryCollection ) { if ( ((GeometryCollection) geometry).getNumGeometries() != 0 ) { return false; } } return true; } return false; } /** * Return true if name is the name of a descriptor of the type or of an ancestor type. * * @param type type to test * @param name name of descriptor * @return true if the type or an ancestor has a descriptor of this name */ private static boolean isValidDescriptor(ComplexType type, Name name) { if (type.getDescriptor(name) != null) { return true; } else if (type.getSuper() instanceof ComplexType) { return isValidDescriptor((ComplexType) type.getSuper(), name); } else { return false; } } }