/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2016, Geomatys * * 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.apache.sis.feature; import com.vividsolutions.jts.geom.Geometry; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import org.apache.sis.internal.feature.AttributeConvention; import org.apache.sis.metadata.iso.citation.Citations; import org.apache.sis.parameter.DefaultParameterDescriptorGroup; import org.apache.sis.util.ArgumentChecks; import org.geotoolkit.geometry.jts.transform.GeometryCSTransformer; import org.geotoolkit.geometry.jts.transform.GeometryTransformer; import org.opengis.feature.Attribute; import org.opengis.feature.AttributeType; import org.opengis.feature.Feature; import org.opengis.feature.FeatureAssociationRole; import org.opengis.feature.FeatureInstantiationException; import org.opengis.feature.FeatureType; import org.opengis.feature.IdentifiedType; import org.opengis.feature.Operation; import org.opengis.feature.Property; import org.opengis.feature.PropertyNotFoundException; import org.opengis.feature.PropertyType; import org.opengis.metadata.Identifier; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.TransformException; import org.opengis.util.FactoryException; import org.opengis.util.GenericName; import org.opengis.util.InternationalString; import org.opengis.util.ScopedName; /** * FeatureType implementation which define a reprojected view of a reference * feature type. * * * @author Johann Sorel (Geomatys) * @module */ public class TransformFeatureType implements DecoratedFeatureType { /** * @todo this is a copy of {@code LinkOperation} package private method. */ static ParameterDescriptorGroup parameters(final String name, final int minimumOccurs, final ParameterDescriptor<?>... parameters) { final Map<String,Object> properties = new HashMap<>(4); properties.put(ParameterDescriptorGroup.NAME_KEY, name); properties.put(Identifier.AUTHORITY_KEY, Citations.SIS); return new DefaultParameterDescriptorGroup(properties, minimumOccurs, 1); } private static final ParameterDescriptorGroup EMPTY_PARAMS = parameters("Reproject", 1); private static final PropertyType AMBIGUOUS = new DefaultAttributeType(Collections.singletonMap("name", "ambiguous"),Object.class,0,0,null); private final FeatureType base; private final Set<GenericName> fullNames = new HashSet<>(); private final Map<String,PropertyType> names = new HashMap<>(); private final GeometryTransformer transformer; private boolean isSimple; private final Map<CoordinateReferenceSystem,GeometryCSTransformer> cache = new HashMap<>(); /** * Filter feature type properties. * * @param base reference feature type * @param targetCRS wanted geometries crs */ public TransformFeatureType(FeatureType base, final GeometryTransformer transformer) { ArgumentChecks.ensureNonNull("type", base); this.base = base; this.transformer = transformer; isSimple = true; for (PropertyType property : base.getProperties(true)) { final int minimumOccurs, maximumOccurs; if (property instanceof AttributeType<?>) { minimumOccurs = ((AttributeType<?>) property).getMinimumOccurs(); maximumOccurs = ((AttributeType<?>) property).getMaximumOccurs(); isSimple &= (minimumOccurs == maximumOccurs); } else if (property instanceof FeatureAssociationRole) { minimumOccurs = ((FeatureAssociationRole) property).getMinimumOccurs(); maximumOccurs = ((FeatureAssociationRole) property).getMaximumOccurs(); isSimple = false; } else { minimumOccurs = 0; maximumOccurs = 0; } if (maximumOccurs != 0) { isSimple &= (maximumOccurs == 1); } //replace operations by ViewOperation if (AttributeConvention.isGeometryAttribute(property)) { property = new TransformOperation(property); } else if(property instanceof Operation) { property = new DecoratedOperation((Operation) property); } final GenericName fullName = property.getName(); fullNames.add(fullName); GenericName name = fullName; names.put(name.toString(), property); while (name instanceof ScopedName) { name = ((ScopedName)name).tail(); if(names.containsKey(name.toString())){ //name is ambigus names.put(name.toString(), AMBIGUOUS); break; }else{ names.put(name.toString(), property); } } //name tip part if(!names.containsKey(name.toString())){ names.put(name.toString(), property); } } } @Override public FeatureType getDecoratedType() { return base; } /** * Redirect to wrapped feature type. * * @return name */ @Override public GenericName getName() { return base.getName(); } /** * Redirect to wrapped feature type. * * @return abstract */ @Override public boolean isAbstract() { return base.isAbstract(); } /** * * @return true if type is simple */ @Override public boolean isSimple() { return isSimple; } /** * Redirect to wrapped feature type. * * @return definition */ @Override public InternationalString getDefinition() { return base.getDefinition(); } /** * Redirect to wrapped feature type. * * @return designation */ @Override public InternationalString getDesignation() { return base.getDesignation(); } /** * Redirect to wrapped feature type. * * @return description */ @Override public InternationalString getDescription() { return base.getDescription(); } @Override public PropertyType getProperty(String name) throws PropertyNotFoundException { final PropertyType type = names.get(name); if (type==null) { throw new PropertyNotFoundException("No property for name "+name); }else if (type==AMBIGUOUS) { throw new PropertyNotFoundException("Ambiguous name "+name); } return type; } @Override public Collection<? extends PropertyType> getProperties(boolean includeSuperTypes) { final Collection<PropertyType> properties = new ArrayList<>(); final Collection<? extends PropertyType> basePropertiers = base.getProperties(includeSuperTypes); for (PropertyType pt : basePropertiers) { if(fullNames.contains(pt.getName())){ properties.add(getProperty(pt.getName().toString())); } } return properties; } @Override public Set<? extends FeatureType> getSuperTypes() { return Collections.EMPTY_SET; } /** * NOTE : copied and modified from DefaultFeatureType.isAssignableFrom * * Returns {@code true} if this type is same or a super-type of the given type. * The check is based mainly on the feature type {@linkplain #getName() name}, which should be unique. * * <div class="note"><b>Analogy:</b> * if we compare {@code FeatureType} to {@link Class} in the Java language, then this method is equivalent * to {@link Class#isAssignableFrom(Class)}.</div> * * @param type the type to be checked. * @return {@code true} if instances of the given type can be assigned to association of this type. */ @Override public boolean isAssignableFrom(final FeatureType type) { if (type == this) { return true; // Optimization for a common case. } ArgumentChecks.ensureNonNull("type", type); return maybeAssignableFrom(this, type); } /** * NOTE : copied and modified from DefaultFeatureType.maybeAssignableFrom * * Returns {@code true} if the given base type may be the same or a super-type of the given type, using only * the name as a criterion. This is a faster check than {@link #isAssignableFrom(FeatureType)}. * * <p>Performance note: callers should verify that {@code base != type} before to invoke this method.</p> */ static boolean maybeAssignableFrom(final FeatureType base, final FeatureType type) { // Slower path for non-SIS implementations. if (Objects.equals(base.getName(), type.getName())) { return true; } for (final FeatureType superType : type.getSuperTypes()) { if (base == superType || maybeAssignableFrom(base, superType)) { return true; } } return false; } @Override public Feature newInstance() throws FeatureInstantiationException, UnsupportedOperationException { return newInstance(base.newInstance()); } public Feature newInstance(Feature base) throws FeatureInstantiationException, UnsupportedOperationException { return new TransformFeature(base); } private Geometry transform(Geometry val) throws FactoryException, TransformException { if (val == null) return val; return transformer.transform(val); } @Override public String toString() { return FeatureFormat.sharedFormat(this); } private class TransformOperation implements Operation { private final PropertyType base; private final AttributeType result; public TransformOperation(PropertyType base) { this.base = base; IdentifiedType result = base; while (result instanceof Operation) { result = ((Operation)result).getResult(); } this.result = (AttributeType) result; } @Override public GenericName getName() { return base.getName(); } @Override public InternationalString getDefinition() { return base.getDefinition(); } @Override public InternationalString getDesignation() { return base.getDesignation(); } @Override public InternationalString getDescription() { return base.getDescription(); } @Override public ParameterDescriptorGroup getParameters() { if (base instanceof Operation) { return ((Operation)base).getParameters(); } else { return EMPTY_PARAMS; } } @Override public IdentifiedType getResult() { return result; } @Override public Property apply(Feature feature, ParameterValueGroup parameters) { if (feature instanceof TransformFeature) { final Geometry value = (Geometry) ((TransformFeature)feature).base.getPropertyValue(base.getName().toString()); final Attribute att = result.newInstance(); try { att.setValue(transform(value)); } catch (FactoryException | TransformException ex) { //TODO replace with geoapi OperationException when available throw new RuntimeException(ex.getMessage(),ex); } return att; }else{ throw new IllegalArgumentException("Invalid input feature, was expecting a feature of type "+TransformFeatureType.this.getName()); } } } private class TransformFeature extends AbstractFeature implements DecoratedFeature{ private final Feature base; private TransformFeature(Feature base) { super(TransformFeatureType.this); this.base = base; } @Override public DecoratedFeatureType getType() { return TransformFeatureType.this; } @Override public Feature getDecoratedFeature() { return base; } @Override public Object getPropertyValue(String name) throws PropertyNotFoundException { final PropertyType prop = names.get(name); if(prop instanceof Operation){ return getOperationValue(name); } return base.getPropertyValue(name); } @Override public void setPropertyValue(String name, Object value) throws IllegalArgumentException { final PropertyType prop = names.get(name); if(prop instanceof Operation){ setOperationValue(name,value); } base.setPropertyValue(name, value); } } }