Package org.locationtech.geogig.geotools.data

Source Code of org.locationtech.geogig.geotools.data.GeogigFeatureSource

/* 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:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.geotools.data;

import java.io.IOException;
import java.util.Set;

import javax.annotation.Nullable;

import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureReader;
import org.geotools.data.FeatureSource;
import org.geotools.data.MaxFeatureReader;
import org.geotools.data.Query;
import org.geotools.data.QueryCapabilities;
import org.geotools.data.Transaction;
import org.geotools.data.sort.SortedFeatureReader;
import org.geotools.data.store.ContentEntry;
import org.geotools.data.store.ContentFeatureSource;
import org.geotools.data.store.ContentState;
import org.geotools.factory.Hints;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.filter.Filters;
import org.geotools.filter.visitor.SimplifyingFilterVisitor;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.renderer.ScreenMap;
import org.locationtech.geogig.api.Context;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.RevFeatureType;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.api.plumbing.RevObjectParse;
import org.locationtech.geogig.geotools.data.GeoGigDataStore.ChangeType;
import org.locationtech.geogig.repository.WorkingTree;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureVisitor;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.sort.SortBy;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;

/**
*
*/
class GeogigFeatureSource extends ContentFeatureSource {

    private static final Logger LOGGER = LoggerFactory.getLogger(GeogigFeatureSource.class);

    private GeoGigDataStore.ChangeType changeType;

    private String oldRoot;

    /**
     * <b>Precondition</b>: {@code entry.getDataStore() instanceof GeoGigDataStore}
     *
     * @param entry
     */
    public GeogigFeatureSource(ContentEntry entry) {
        this(entry, (Query) null);
    }

    /**
     * <b>Precondition</b>: {@code entry.getDataStore() instanceof GeoGigDataStore}
     *
     * @param entry
     * @param query optional "definition query" making this feature source a "view"
     */
    public GeogigFeatureSource(ContentEntry entry, @Nullable Query query) {
        super(entry, query);
        Preconditions.checkArgument(entry.getDataStore() instanceof GeoGigDataStore);
    }

    /**
     * Adds the {@link Hints#FEATURE_DETACHED} hint to the supported hints so the renderer doesn't
     * clone the geometries
     */
    @Override
    protected void addHints(Set<Hints.Key> hints) {
        hints.add(Hints.FEATURE_DETACHED);
        hints.add(Hints.SCREENMAP);
    }

    @Override
    protected boolean canFilter() {
        return true;
    }

    @Override
    protected boolean canSort() {
        return true;
    }

    @Override
    protected boolean canRetype() {
        return false;
    }

    @Override
    protected boolean canLimit() {
        return true;
    }

    @Override
    protected boolean canOffset() {
        return true;
    }

    /**
     * @return {@code true}
     */
    @Override
    protected boolean canTransact() {
        return true;
    }

    @Override
    protected boolean handleVisitor(Query query, FeatureVisitor visitor) throws IOException {
        return false;
    }

    @Override
    public GeoGigDataStore getDataStore() {
        return (GeoGigDataStore) super.getDataStore();
    }

    @Override
    public ContentState getState() {
        return super.getState();
    }

    /**
     * Overrides {@link ContentFeatureSource#getName()} to restore back the original meaning of
     * {@link FeatureSource#getName()}
     */
    @Override
    public Name getName() {
        return getEntry().getName();
    }

    /**
     * Creates a {@link QueryCapabilities} that declares support for
     * {@link QueryCapabilities#isUseProvidedFIDSupported() isUseProvidedFIDSupported}, the
     * datastore supports using the provided feature id in the data insertion workflow as opposed to
     * generating a new id, by looking into the user data map ( {@link Feature#getUserData()}) for a
     * {@link Hints#USE_PROVIDED_FID} key associated to a {@link Boolean#TRUE} value, if the
     * key/value pair is there an attempt to use the provided id will be made, and the operation
     * will fail if the key cannot be parsed into a valid storage identifier.
     */
    @Override
    protected QueryCapabilities buildQueryCapabilities() {
        return new QueryCapabilities() {
            /**
             * @return {@code true}
             */
            @Override
            public boolean isUseProvidedFIDSupported() {
                return true;
            }

            /**
             *
             * @return {@code false} by now, will see how/whether we'll support
             *         {@link Query#getVersion()} later
             */
            @Override
            public boolean isVersionSupported() {
                return false;
            }

        };
    }

    @Override
    protected ReferencedEnvelope getBoundsInternal(Query query) throws IOException {
        final Filter filter = (Filter) query.getFilter().accept(new SimplifyingFilterVisitor(),
                null);
        final CoordinateReferenceSystem crs = getSchema().getCoordinateReferenceSystem();
        if (Filter.INCLUDE.equals(filter) && oldRoot == null
                && ChangeType.ADDED.equals(changeType())) {
            NodeRef typeRef = getTypeRef();
            ReferencedEnvelope bounds = new ReferencedEnvelope(crs);
            typeRef.getNode().expand(bounds);
            return bounds;
        }
        if (Filter.EXCLUDE.equals(filter)) {
            return ReferencedEnvelope.create(crs);
        }

        FeatureReader<SimpleFeatureType, SimpleFeature> features;
        if (isNaturalOrder(query.getSortBy())) {
            Integer offset = query.getStartIndex();
            Integer maxFeatures = query.getMaxFeatures() == Integer.MAX_VALUE ? null : query
                    .getMaxFeatures();
            ScreenMap screenMap = (ScreenMap) query.getHints().get(Hints.SCREENMAP);
            features = getNativeReader(Query.NO_NAMES, filter, offset, maxFeatures, screenMap);
        } else {
            features = getReader(query);
        }
        ReferencedEnvelope bounds = new ReferencedEnvelope(crs);
        try {
            while (features.hasNext()) {
                bounds.expandToInclude((ReferencedEnvelope) features.next().getBounds());
            }
        } finally {
            features.close();
        }
        return bounds;
    }

    @Override
    protected int getCountInternal(Query query) throws IOException {
        final Filter filter = (Filter) query.getFilter().accept(new SimplifyingFilterVisitor(),
                null);
        if (Filter.EXCLUDE.equals(filter)) {
            return 0;
        }

        final Integer offset = query.getStartIndex();
        final Integer maxFeatures = query.getMaxFeatures() == Integer.MAX_VALUE ? null : query
                .getMaxFeatures();

        int size;
        if (Filter.INCLUDE.equals(filter) && oldRoot == null
                && ChangeType.ADDED.equals(changeType())) {
            RevTree tree = getTypeTree();
            size = (int) tree.size();
            if (offset != null) {
                size = size - offset.intValue();
            }
            if (maxFeatures != null) {
                size = Math.min(size, maxFeatures.intValue());
            }
            return size;
        }

        FeatureReader<SimpleFeatureType, SimpleFeature> features;
        if (isNaturalOrder(query.getSortBy())) {
            ScreenMap screenMap = (ScreenMap) query.getHints().get(Hints.SCREENMAP);
            features = getNativeReader(Query.NO_NAMES, filter, offset, maxFeatures, screenMap);
        } else {
            features = getReader(query);
        }
        int count = 0;
        try {
            while (features.hasNext()) {
                features.next();
                count++;
            }
        } finally {
            features.close();
        }

        return count;
    }

    @Override
    protected FeatureReader<SimpleFeatureType, SimpleFeature> getReaderInternal(final Query query)
            throws IOException {

        FeatureReader<SimpleFeatureType, SimpleFeature> reader;

        final boolean naturalOrder = isNaturalOrder(query.getSortBy());
        final int startIndex = Optional.fromNullable(query.getStartIndex()).or(Integer.valueOf(0));
        final Integer maxFeatures = query.getMaxFeatures() == Integer.MAX_VALUE ? null : query
                .getMaxFeatures();
        final Filter filter = query.getFilter();
        final ScreenMap screenMap = (ScreenMap) query.getHints().get(Hints.SCREENMAP);
        final String[] propertyNames = query.getPropertyNames();
        if (naturalOrder) {
            reader = getNativeReader(propertyNames, filter, startIndex, maxFeatures, screenMap);
        } else {
            reader = getNativeReader(propertyNames, filter, null, null, screenMap);
            // sorting
            reader = new SortedFeatureReader(DataUtilities.simple(reader), query);
            if (startIndex > 0) {
                // skip the first n records
                for (int i = 0; i < startIndex && reader.hasNext(); i++) {
                    reader.next();
                }
            }
            if (maxFeatures != null && maxFeatures > 0) {
                reader = new MaxFeatureReader<SimpleFeatureType, SimpleFeature>(reader, maxFeatures);
            }
        }

        return reader;
    }

    private boolean isNaturalOrder(@Nullable SortBy[] sortBy) {
        if (sortBy == null || sortBy.length == 0
                || (sortBy.length == 1 && SortBy.NATURAL_ORDER.equals(sortBy[0]))) {
            return true;
        }
        return false;
    }

    /**
     * @param propertyNames properties to retrieve, empty array for no properties at all
     *        {@link Query#NO_NAMES}, {@code null} means all properties {@link Query#ALL_NAMES}
     */
    private FeatureReader<SimpleFeatureType, SimpleFeature> getNativeReader(
            @Nullable String[] propertyNames, Filter filter, @Nullable Integer offset,
            @Nullable Integer maxFeatures, @Nullable final ScreenMap screenMap) {

        if (screenMap == null) {
            LOGGER.trace("GeoGigFeatureSource.getNativeReader: no screenMap provided");
        } else {
            LOGGER.trace("GeoGigFeatureSource.getNativeReader: using screenMap filter");
        }
        LOGGER.trace("Query filter: {}", filter);
        filter = (Filter) filter.accept(new SimplifyingFilterVisitor(), null);
        LOGGER.trace("Simplified filter: {}", filter);

        GeogigFeatureReader<SimpleFeatureType, SimpleFeature> nativeReader;

        final String rootRef = getRootRef();
        final String featureTypeTreePath = getTypeTreePath();

        final SimpleFeatureType fullType = getSchema();

        boolean ignoreAttributes = false;
        if (propertyNames != null && propertyNames.length == 0) {
            String[] inProcessFilteringAttributes = Filters.attributeNames(filter, fullType);
            ignoreAttributes = inProcessFilteringAttributes.length == 0;
        }

        final String compareRootRef = oldRoot();
        final GeoGigDataStore.ChangeType changeType = changeType();
        final Context context = getCommandLocator();

        nativeReader = new GeogigFeatureReader<SimpleFeatureType, SimpleFeature>(context, fullType,
                filter, featureTypeTreePath, rootRef, compareRootRef, changeType, offset,
                maxFeatures, screenMap, ignoreAttributes);
        return nativeReader;
    }

    public void setChangeType(GeoGigDataStore.ChangeType changeType) {
        this.changeType = changeType;
    }

    public void setOldRoot(@Nullable String oldRoot) {
        this.oldRoot = oldRoot;
    }

    private String oldRoot() {
        return oldRoot == null ? ObjectId.NULL.toString() : oldRoot;
    }

    private GeoGigDataStore.ChangeType changeType() {
        return changeType == null ? ChangeType.ADDED : changeType;
    }

    @Override
    protected SimpleFeatureType buildFeatureType() throws IOException {

        SimpleFeatureType featureType = getNativeType();

        final Name name = featureType.getName();
        final Name assignedName = getEntry().getName();

        if (assignedName.getNamespaceURI() != null && !assignedName.equals(name)) {
            SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
            builder.init(featureType);
            builder.setName(assignedName);
            featureType = builder.buildFeatureType();
        }
        return featureType;
    }

    Context getCommandLocator() {
        Context commandLocator = getDataStore().getCommandLocator(getTransaction());
        return commandLocator;
    }

    SimpleFeatureType getNativeType() {

        final NodeRef typeRef = getTypeRef();
        final String treePath = typeRef.path();
        final ObjectId metadataId = typeRef.getMetadataId();

        Context commandLocator = getCommandLocator();
        Optional<RevFeatureType> revType = commandLocator.command(RevObjectParse.class)
                .setObjectId(metadataId).call(RevFeatureType.class);

        if (!revType.isPresent()) {
            throw new IllegalStateException(String.format("Feature type for tree %s not found",
                    treePath));
        }

        SimpleFeatureType featureType = (SimpleFeatureType) revType.get().type();
        return featureType;
    }

    String getTypeTreePath() {
        NodeRef typeRef = getTypeRef();
        String path = typeRef.path();
        return path;
    }

    /**
     * @return
     */
    NodeRef getTypeRef() {
        GeoGigDataStore dataStore = getDataStore();
        Name name = getName();
        Transaction transaction = getTransaction();
        return dataStore.findTypeRef(name, transaction);
    }

    /**
     * @return
     */
    RevTree getTypeTree() {
        String refSpec = getRootRef() + ":" + getTypeTreePath();
        Context commandLocator = getCommandLocator();
        Optional<RevTree> ref = commandLocator.command(RevObjectParse.class).setRefSpec(refSpec)
                .call(RevTree.class);
        Preconditions.checkState(ref.isPresent(), "Ref %s not found on working tree", refSpec);
        return ref.get();
    }

    private String getRootRef() {
        GeoGigDataStore dataStore = getDataStore();
        Transaction transaction = getTransaction();
        return dataStore.getRootRef(transaction);
    }

    /**
     * @return
     */
    WorkingTree getWorkingTree() {
        Context commandLocator = getCommandLocator();
        WorkingTree workingTree = commandLocator.workingTree();
        return workingTree;
    }
}
TOP

Related Classes of org.locationtech.geogig.geotools.data.GeogigFeatureSource

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.