org.polymap.p4.layer.FeatureLayer.java Source code

Java tutorial

Introduction

Here is the source code for org.polymap.p4.layer.FeatureLayer.java

Source

/* 
 * polymap.org
 * Copyright (C) 2016, Falko Brutigam. All rights reserved.
 *
 * This 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; either version 3.0 of
 * the License, or (at your option) any later version.
 *
 * This software 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.polymap.p4.layer;

import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.geotools.data.DataStore;
import org.opengis.feature.Feature;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.eclipse.core.runtime.IProgressMonitor;
import org.polymap.core.data.DataPlugin;
import org.polymap.core.data.PipelineFeatureSource;
import org.polymap.core.data.feature.FeaturesProducer;
import org.polymap.core.data.pipeline.DataSourceDescription;
import org.polymap.core.data.pipeline.Pipeline;
import org.polymap.core.data.pipeline.PipelineIncubationException;
import org.polymap.core.project.ILayer;
import org.polymap.core.runtime.JobExecutor;
import org.polymap.core.runtime.UIJob;
import org.polymap.core.runtime.UIThreadExecutor;
import org.polymap.core.runtime.cache.Cache;
import org.polymap.core.runtime.event.EventManager;
import org.polymap.core.runtime.session.SessionSingleton;

import org.polymap.p4.P4Panel;
import org.polymap.p4.catalog.AllResolver;
import org.polymap.p4.data.P4PipelineIncubator;

/**
 * Carries the {@link FeatureSource}, the {@link #filter()}ed features and the
 * {@link #clicked()} feature for layers which are connected to a {@link DataStore}.
 * There is one instance per layer per session, retrieved via {@link #of(ILayer)}.
 * <p/>
 * The currently <b>active</b> layer and its selection is stored in
 * {@link P4Panel#featureSelection()}.
 *
 * @author <a href="http://www.polymap.de">Falko Brutigam</a>
 */
public class FeatureLayer {

    private static Log log = LogFactory.getLog(FeatureLayer.class);

    public static final FilterFactory2 ff = DataPlugin.ff;

    /**
     * The selection modes.
     */
    public enum Mode {
        REPLACE, ADD, DIFFERENCE, INTERSECT
    };

    /**
     * Waits for the {@link FeatureLayer} of the given {@link ILayer}.
     * <p/>
     * Avoid calling just {@link CompletableFuture#get()} as this may block the
     * calling (UI) thread. Instead register callbacks that handle the result
     * asynchronously.
     * <p/>
     * The callbacks are called from within an {@link UIJob}. Use
     * {@link UIThreadExecutor} to do somethinf in the display thread.
     * <p/>
     * <b>Example usage:</b>
     * <pre>
     *      FeatureLayer.of( layer ).thenAccept( featureLayer -> {
     *          if (featureLayer.isPresent()) {
     *              ...
     *          }
     *          else {
     *              ...
     *          }
     *      })
     *      .exceptionally( e -> {
     *          StatusDispatcher.handleError( "", e );
     *          return null;
     *      });
     * </pre>
     * 
     * @param layer
     */
    public static CompletableFuture<Optional<FeatureLayer>> of(ILayer layer) {
        return CompletableFuture.supplyAsync(() -> {
            SessionHolder session = SessionHolder.instance(SessionHolder.class);
            FeatureLayer result = session.instances.computeIfAbsent((String) layer.id(), key -> {
                try {
                    IProgressMonitor monitor = UIJob.monitorOfThread();
                    return new FeatureLayer(layer).doConnectLayer(monitor);
                } catch (Exception e) {
                    throw new CompletionException(e);
                }
            });
            return result.isValid() ? Optional.of(result) : Optional.empty();
        }, JobExecutor.instance());
    }

    private static class SessionHolder extends SessionSingleton {

        /**
         * Using {@link ConcurrentHashMap} instead of {@link Cache} ensures that
         * mapping function is executed at most once per key and just one
         * {@link FeatureLayer} instance is constructed per {@link ILayer}.
         */
        ConcurrentMap<String, FeatureLayer> instances = new ConcurrentHashMap(32);
    }

    // instance *******************************************

    private ILayer layer;

    private PipelineFeatureSource fs;

    private Filter filter = Filter.INCLUDE;

    private Optional<Feature> clicked = Optional.empty();

    protected FeatureLayer(ILayer layer) {
        this.layer = layer;
    }

    protected FeatureLayer doConnectLayer(IProgressMonitor monitor) throws PipelineIncubationException, Exception {
        assert fs == null;
        log.info("doConnectLayer(): " + layer.label.get());
        // resolve service
        DataSourceDescription dsd = AllResolver.instance().connectLayer(layer, monitor)
                .orElseThrow(() -> new RuntimeException("No data source for layer: " + layer));

        // create pipeline for it
        Pipeline pipeline = P4PipelineIncubator.forLayer(layer).newPipeline(FeaturesProducer.class, dsd, null);
        if (pipeline != null && pipeline.length() > 0) {
            fs = new PipelineFeatureSource(pipeline);
        }
        return this;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (obj instanceof FeatureLayer) {
            FeatureLayer rhs = (FeatureLayer) obj;
            return layer.id().equals(rhs.layer.id());
        } else {
            return false;
        }
    }

    public boolean isValid() {
        return fs != null;
    }

    public PipelineFeatureSource featureSource() {
        assert isValid();
        return fs;
    }

    public ILayer layer() {
        assert isValid();
        return layer;
    }

    public Filter filter() {
        assert isValid();
        return filter;
    }

    public void select(Filter selection, Mode mode) {
        assert isValid();
        Filter old = filter;
        switch (mode) {
        case REPLACE:
            filter = selection;
            break;
        case ADD:
            filter = ff.or(filter, selection);
            break;
        default:
            throw new RuntimeException("Unhandled mode: " + mode);
        }
        // event
        EventManager.instance().publish(new FeatureSelectionEvent(this, filter, old));
    }

    /**
     * The one feature that was 'clicked' somewhere in the UI. Usually a feature can
     * be clicked in the map and a feature table.
     */
    public Optional<Feature> clicked() {
        assert isValid();
        return clicked;
    }

    /**
     * 
     *
     * @param clicked The newly {@link #clicked()} feature, or null if currently
     *        clicked feature should be un-clicked.
     */
    public void setClicked(Feature clicked) {
        assert isValid();
        Optional<Feature> previous = this.clicked;
        this.clicked = Optional.ofNullable(clicked);
        EventManager.instance().publish(new FeatureClickEvent(this, this.clicked, previous));
    }

}