Package org.locationtech.geogig.osm.internal

Source Code of org.locationtech.geogig.osm.internal.MappingRule

/* Copyright (c) 2013-2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Victor Olaya (Boundless) - initial implementation
*/
package org.locationtech.geogig.osm.internal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import javax.annotation.Nullable;

import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.CRS;
import org.locationtech.geogig.storage.FieldType;
import org.locationtech.geogig.storage.text.TextValueSerializer;
import org.opengis.feature.Feature;
import org.opengis.feature.GeometryAttribute;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.openstreetmap.osmosis.core.domain.v0_6.Tag;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.gson.annotations.Expose;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;

/**
* A rule used to convert an OSM entity into a feature with a custom feature type. Attributes values
* for the attributes in the feature type are taken from the tags and geometry of the feature to
* convert
*
*/
public class MappingRule {

    public enum GeomRestriction {
        ONLY_OPEN_LINES("open"), ONLY_CLOSED_LINES("closed"), ALL_LINESTRINGS("all");

        private String s;

        GeomRestriction(String s) {
            this.s = s;
        }

        public String getModifierString() {
            return s;
        }

        public static GeomRestriction valueFromText(String s) {
            for (GeomRestriction gr : values()) {
                if (gr.getModifierString().equals(s)) {
                    return gr;
                }
            }
            throw new NoSuchElementException("Unknown modifier: " + s);
        }
    }

    public enum DefaultField {
        visible(Boolean.class), timestamp(Long.class), tags(String.class), changeset(Long.class), version(
                Integer.class), user(String.class);

        private Class<?> clazz;

        private static ArrayList<Object> names = Lists.newArrayList();
        static {
            for (DefaultField v : DefaultField.values()) {
                names.add(v.name());
            }
        }

        DefaultField(Class<?> clazz) {
            this.clazz = clazz;
        }

        public Class<?> getFieldClass() {
            return clazz;
        }

        public static boolean isDefaultField(String s) {
            return names.contains(s);
        }

    }

    /**
     * The name of the rule
     */
    @Expose
    private String name;

    /**
     * A map of key, list_of_accepted_values, to be used to filter features. If a feature has any of
     * the keys in this map with any of the accepted values, it will be transformed by this rule
     */
    @Expose
    private Map<String, List<String>> filter;

    /**
     * A map of key, list_of_values_to_exclude, to be used to filter features. If a feature has any
     * of the keys in this map with any of the values in the list, it will not be transformed by
     * this rule, even if it meets the conditions set by the 'filter' object
     */
    @Expose
    @Nullable
    private Map<String, List<String>> exclude;

    /**
     * The fields to use for the custom feature type of the transformed feature
     */
    @Expose
    private Map<String, AttributeDefinition> fields;

    /**
     * The default fields to include in the destination feature type without transforming them
     */
    @Expose
    @Nullable
    private List<DefaultField> defaultFields;

    private SimpleFeatureType featureType;

    private SimpleFeatureBuilder featureBuilder;

    private Class<?> geometryType;

    private GeomRestriction geomRestriction;

    private ArrayList<String> _mandatoryTags = null;

    private static GeometryFactory gf = new GeometryFactory();

    public MappingRule(final String name, final Map<String, List<String>> filter,
            @Nullable final Map<String, List<String>> filterExclude,
            final Map<String, AttributeDefinition> fields,
            @Nullable final List<DefaultField> defaultFields) {

        Preconditions.checkNotNull(name);
        Preconditions.checkNotNull(filter);
        Preconditions.checkNotNull(fields);
        this.name = name;
        this.filter = filter;
        this.exclude = filterExclude;
        this.fields = fields;
        this.defaultFields = defaultFields;
        ArrayList<String> names = Lists.newArrayList();
        for (AttributeDefinition ad : fields.values()) {
            Preconditions.checkState(!names.contains(ad.getName()),
                    "Duplicated alias in mapping rule: " + ad.getName());
            names.add(ad.getName());
        }
    }

    /**
     * Returns the feature type defined by this rule. This is the feature type that features
     * transformed by this rule will have
     *
     * @return
     */
    public SimpleFeatureType getFeatureType() {
        if (featureType == null) {
            SimpleFeatureTypeBuilder fb = new SimpleFeatureTypeBuilder();
            fb.setName(name);
            fb.add("id", Long.class);
            if (defaultFields != null) {
                for (DefaultField df : defaultFields) {
                    fb.add(df.name().toLowerCase(), df.getFieldClass());
                }
            }
            Set<String> keys = this.fields.keySet();
            for (String key : keys) {
                AttributeDefinition field = fields.get(key);
                Class<?> clazz = field.getType().getBinding();
                if (Geometry.class.isAssignableFrom(clazz)) {
                    Preconditions.checkArgument(geometryType == null,
                            "The mapping has more than one geometry attribute");
                    CoordinateReferenceSystem epsg4326;
                    try {
                        epsg4326 = CRS.decode("EPSG:4326", true);
                        fb.add(field.getName(), clazz, epsg4326);
                    } catch (NoSuchAuthorityCodeException e) {
                    } catch (FactoryException e) {
                    }
                    geometryType = clazz;
                } else {
                    fb.add(field.getName(), clazz);
                }
            }
            Preconditions.checkNotNull(geometryType,
                    "The mapping rule does not define a geometry field");
            if (!geometryType.equals(Point.class)) {
                fb.add("nodes", String.class);
            }
            featureType = fb.buildFeatureType();

            featureBuilder = new SimpleFeatureBuilder(featureType);

        }
        return featureType;

    }

    private GeomRestriction getGeomRestriction() {
        if (geomRestriction == null) {
            if (filter.containsKey("geom")) {
                geomRestriction = GeomRestriction.valueFromText(filter.get("geom").get(0));
            } else {
                geomRestriction = GeomRestriction.ALL_LINESTRINGS;
            }
        }
        return geomRestriction;
    }

    /**
     * Returns the feature resulting from transforming a given feature using this rule
     *
     * @param feature
     * @return
     */
    public Optional<Feature> apply(Feature feature) {
        String tagsString = (String) ((SimpleFeature) feature).getAttribute("tags");
        Collection<Tag> tags = OSMUtils.buildTagsCollectionFromString(tagsString);
        return apply(feature, tags);
    }

    /**
     * Returns the feature resulting from transforming a given feature using this rule. This method
     * takes a collection of tags, so there is no need to compute them from the 'tags' attribute.
     * This is meant as a faster alternative to the apply(Feature) method, in case the mapping
     * object calling this has already computed the tags, to avoid recomputing them
     *
     * @param feature
     * @param tags
     * @return
     */
    public Optional<Feature> apply(Feature feature, Collection<Tag> tags) {
        if (!canBeApplied(feature, tags)) {
            return Optional.absent();
        }
        for (AttributeDescriptor attribute : getFeatureType().getAttributeDescriptors()) {
            String attrName = attribute.getName().toString();
            Class<?> clazz = attribute.getType().getBinding();
            if (Geometry.class.isAssignableFrom(clazz)) {
                Geometry geom = prepareGeometry((Geometry) feature.getDefaultGeometryProperty()
                        .getValue());
                if (geom == null) {
                    return Optional.absent();
                }
                featureBuilder.set(attrName, geom);
            } else {
                Object value = null;
                for (Tag tag : tags) {
                    if (fields.containsKey(tag.getKey())) {
                        if (fields.get(tag.getKey()).getName().equals(attrName)) {
                            FieldType type = FieldType.forBinding(clazz);
                            value = getAttributeValue(tag.getValue(), type);
                            break;
                        }
                    }
                }
                featureBuilder.set(attribute.getName(), value);
            }
        }

        String id = feature.getIdentifier().getID();
        featureBuilder.set("id", id);
        if (defaultFields != null) {
            for (DefaultField df : defaultFields) {
                featureBuilder.set(df.name(), feature.getProperty(df.name()).getValue());
            }
        }
        if (!featureType.getGeometryDescriptor().getType().getBinding().equals(Point.class)) {
            featureBuilder.set("nodes", feature.getProperty("nodes").getValue());
        }
        return Optional.of((Feature) featureBuilder.buildFeature(id));

    }

    private Geometry prepareGeometry(Geometry geom) {
        if (geometryType.equals(Polygon.class)) {
            Coordinate[] coords = geom.getCoordinates();
            if (!coords[0].equals(coords[coords.length - 1])) {
                Coordinate[] newCoords = new Coordinate[coords.length + 1];
                System.arraycopy(coords, 0, newCoords, 0, coords.length);
                newCoords[coords.length] = coords[0];
                coords = newCoords;
            }
            if (coords.length < 4) {
                return null;
            }
            return gf.createPolygon(coords);
        }

        return geom;
    }

    private Object getAttributeValue(String value, FieldType type) {
        return TextValueSerializer.fromString(type, value);
    }

    public boolean canBeApplied(Feature feature, Collection<Tag> tags) {
        return hasCorrectTags(feature, tags) && hasCompatibleGeometryType(feature);
    }

    private boolean hasCompatibleGeometryType(Feature feature) {
        getFeatureType();
        GeomRestriction restriction = getGeomRestriction();
        GeometryAttribute property = feature.getDefaultGeometryProperty();
        Geometry geom = (Geometry) property.getValue();
        if (geom.getClass().equals(Point.class)) {
            return geometryType == Point.class;
        } else {
            if (geometryType.equals(Point.class)) {
                return false;
            }
            Coordinate[] coords = geom.getCoordinates();
            if (geometryType.equals(Polygon.class) && coords.length < 3) {
                return false;
            }
            boolean isClosed = coords[0].equals(coords[coords.length - 1]);
            if (isClosed && restriction.equals(GeomRestriction.ONLY_OPEN_LINES)) {
                return false;
            }
            if (!isClosed && restriction.equals(GeomRestriction.ONLY_CLOSED_LINES)) {
                return false;
            }
            return true;
        }
    }

    private boolean hasCorrectTags(Feature feature, Collection<Tag> tags) {
        if (filter.isEmpty() || (filter.size() == 1 && filter.containsKey("geom"))
                && (exclude == null || exclude.isEmpty())) {
            return true;
        }
        boolean ret = false;
        ArrayList<String> tagNames = Lists.newArrayList();
        for (Tag tag : tags) {
            tagNames.add(tag.getKey());
            if (exclude != null && exclude.keySet().contains(tag.getKey())) {
                List<String> values = exclude.get(tag.getKey());
                if (values != null) {
                    if (values.isEmpty() || values.contains(tag.getValue())) {
                        return false;
                    }
                }
            }
            if (filter.keySet().contains(tag.getKey())) {
                List<String> values = filter.get(tag.getKey());
                if (values.isEmpty() || values.contains(tag.getValue())) {
                    ret = true;
                }
            }

        }
        if (ret) {
            for (String mandatory : getMandatoryTags()) {
                if (!tagNames.contains(mandatory)) {
                    return false;
                }
            }
        }
        return ret;
    }

    public String getName() {
        return name;
    }

    /**
     * Returns true if this rule generates feature with a line or polygon geometry, or it doesn't
     * have a geometry attribute, so it can take ways as inputs
     *
     * @return
     */
    public boolean canUseWays() {
        getFeatureType();
        return !geometryType.equals(Point.class);
    }

    /**
     * Returns true if this rule generates feature with a point geometry, or it doesn't have a
     * geometry attribute, so it can take nodes as inputs
     *
     * @return
     */
    public boolean canUseNodes() {
        getFeatureType();
        return geometryType.equals(Point.class);
    }

    /**
     * Resolves the original tag name based on the name of a field created by this mapping rule (an
     * alias for a tag name) *
     *
     * @param field the name of the field
     * @return the name of the tag from which the passed field was created in the specified mapped
     *         tree. If the alias cannot be resolved, that passed alias itself is returned
     */
    public String getTagNameFromAlias(String alias) {
        Set<String> keys = this.fields.keySet();
        for (String key : keys) {
            AttributeDefinition field = fields.get(key);
            if (field.getName().equals(alias)) {
                return key;
            }
        }
        return alias;
    }

    public boolean equals(Object o) {
        if (o instanceof MappingRule) {
            MappingRule m = (MappingRule) o;
            return name.equals(m.name) && m.fields.equals(fields) && m.filter.equals(filter)
                    && m.exclude.equals(exclude) && m.defaultFields.equals(defaultFields);
        } else {
            return false;
        }
    }

    private ArrayList<String> getMandatoryTags() {
        if (_mandatoryTags == null) {
            _mandatoryTags = Lists.newArrayList();
            if (exclude != null) {
                for (String key : this.exclude.keySet()) {
                    if (exclude.get(key) == null) {
                        _mandatoryTags.add(key);
                    }
                }
            }
        }
        return _mandatoryTags;

    }
}
TOP

Related Classes of org.locationtech.geogig.osm.internal.MappingRule

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.