Package org.locationtech.udig.project.ui.internal

Source Code of org.locationtech.udig.project.ui.internal.LayerGeneratedGlyphDecorator

/* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2004-2012, Refractions Research Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
* License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
*/
package org.locationtech.udig.project.ui.internal;

import static org.locationtech.udig.project.internal.provider.LayerItemProvider.GENERATED_ICON;
import static org.locationtech.udig.project.internal.provider.LayerItemProvider.GENERATED_NAME;

import java.io.IOException;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import org.locationtech.udig.catalog.IGeoResource;
import org.locationtech.udig.catalog.IGeoResourceInfo;
import org.locationtech.udig.catalog.ITransientResolve;
import org.locationtech.udig.project.ILayer;
import org.locationtech.udig.project.internal.Layer;
import org.locationtech.udig.project.internal.ProjectPackage;
import org.locationtech.udig.project.internal.StyleBlackboard;
import org.locationtech.udig.ui.ImageCache;
import org.locationtech.udig.ui.graphics.Glyph;
import org.locationtech.udig.ui.graphics.SLDs;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.swt.graphics.Image;
import org.geotools.data.FeatureSource;
import org.geotools.data.wms.WebMapServer;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.Style;
import org.geotools.styling.Symbolizer;
import org.opengis.coverage.grid.GridCoverageReader;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.GeometryDescriptor;

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;

/**
* Generate glyph/title - fetch from WMS or derrive from StyleBlackboard.
* <p>
* This is a complete heavyweight decorator - there is no messing around with this one. It has its
* own thread, and will pay attention to events.
* </p>
* <p>
* Generated Content is placed in layer properties:
* <ul>
* <li>displayName:
* <li>displayGlyph:
* </ul>
* </p>
* <p>
* Generation only kicks in if getGlyph or getName return null.
* </p>
*
* @author jgarnett
* @since 0.7.0
*/
public class LayerGeneratedGlyphDecorator implements ILabelDecorator {

    /**
     * Queue of layers needing to be refreshed.
     * <p>
     * Does not allow duplicates to be added.
     * </p>
     */
    LinkedList<Layer> queue = new LinkedList<Layer>(){
        /** <code>serialVersionUID</code> field */
        private static final long serialVersionUID = 3834874663317747760L;

        public void add( int index, Layer aLayer ) {
            if (!contains(aLayer))
                super.add(index, aLayer);
        }

        public boolean add( Layer aLayer ) {
            if (!contains(aLayer))
                return super.add(aLayer);
            return false;
        }

        public void addFirst( Layer aLayer ) {
            if (!contains(aLayer))
                super.addFirst(aLayer);

        }

        public void addLast( Layer aLayer ) {
            if (!contains(aLayer))
                super.addFirst(aLayer);
        }
    };

    private volatile boolean disposed=false;

    /**
     * Piccaso generates pcitures for layers in the queue.
     * <p>
     * This is the sole provider of dynamic artwork for layers. Piccaso will block contacting
     * external servers and so on.
     * </p>
     * <p>
     * If this gets to be a pain we may switch to the dutch school model, perhaps even
     * impressionests based on a sample feature.
     * </p>
     * <p>
     * Any artwork is provided as an ImageDescriptor using the key GENERATED_ICON. This will be
     * turned into an Image by the decorateImage method as required.
     * </p>
     */
    Job picasso = new Job(Messages.LayerGeneratedGlyphDecorator_jobName){

        @SuppressWarnings("unchecked")
        public IStatus run( IProgressMonitor monitor ) {
            Layer layer = null;
            SERVICE: while( !disposed  ) {
                synchronized (queue) {
                    if (queue.isEmpty()) {
                        return Status.OK_STATUS;
                    }

                    try {
                        layer = queue.removeFirst();
                        if (!layer.eAdapters().contains(adapterImpl)) {
                            layer.eAdapters().add(adapterImpl);
                        }
                    } catch (NoSuchElementException noLayer) {
                        continue SERVICE;
                    }
                }
                try {
                    boolean notifyIcon = refreshIcon(layer);
                   
                    boolean notifyLabel = refreshLabel(layer);

                    if (notifyIcon || notifyLabel) {
                        refresh(layer);
                    }
                } catch (Throwable t) {
                    // must catch all icon errors or this thread will die :-P
                }
            }
            return Status.OK_STATUS;

        }

        /**
         * Refresh icon if required, true if label was changed.
         * <p>
         * Icon will be placed on GENERATED_ICON
         * </p>
         *
         * @param layer
         * @param notify
         * @return true if label was changed
         */
        private boolean refreshIcon( Layer layer ) {
            try {
                ImageDescriptor icon = generateIcon(layer);
                if (icon != null) {
                    layer.getProperties().put(GENERATED_ICON, icon);
                    return true;
                }
            } catch (Throwable problem) {
                ProjectUIPlugin.getDefault().getLog().log(
                        new Status(IStatus.WARNING, ProjectUIPlugin.ID, IStatus.INFO,
                                "Could not generate layer glyph " + layer, problem)); //$NON-NLS-1$
                // layer.setStatus(Layer.WARNING);
            }
            return false;
        }

        /**
         * Refresh icon if required, true if icon was changed.
         *
         * @param layer
         * @param notify
         * @return ture if icon was changed
         */
        private boolean refreshLabel( Layer layer ) {
            String label = label(layer);

            if (label == null || label.length() == 0) {
                try {
                    String gen = generateLabel(layer);
                    // System.out.println( "generated "+ gen );
                    if (gen != null) {
                        layer.getProperties().putString(GENERATED_NAME, gen);
                        return true;
                    }
                } catch (Throwable problem) {
                    ProjectUIPlugin.getDefault().getLog().log(
                            new Status(IStatus.WARNING, ProjectUIPlugin.ID, IStatus.INFO,
                                    "Could not generate name for " + layer, problem)); //$NON-NLS-1$
                    // layer.setStatus(Layer.WARNING);
                }
            }
            return false;
        }
    };

    private static LayerGeneratedGlyphDecorator instance = null;

    public static LayerGeneratedGlyphDecorator getInstance() {
        return instance;
    }

    public LayerGeneratedGlyphDecorator() {
        picasso.setSystem(true);
        picasso.setPriority(Job.DECORATE);
        picasso.schedule();
        instance=this;
    }

    Set<ILabelProviderListener> listeners = new CopyOnWriteArraySet<ILabelProviderListener>();

    Adapter adapterImpl = new AdapterImpl(){
        public void notifyChanged( Notification msg ) {
            if (msg.getNotifier() instanceof Layer) {
                final Layer layer = (Layer) msg.getNotifier();
                if (queue == null) {
                    // we can stop listening now nobody cares
                    layer.eAdapters().remove(this);
                    return;
                }
                switch( msg.getFeatureID(Layer.class) ) {
                case ProjectPackage.LAYER__ICON:
                case ProjectPackage.LAYER__STYLE_BLACKBOARD:
                case ProjectPackage.LAYER__NAME:
                case ProjectPackage.LAYER__GEO_RESOURCES:
                    layer.getProperties().put(GENERATED_ICON, null);
                    layer.getProperties().put(GENERATED_NAME, null);
                    refresh(layer);
                    break;
                }
            }
        }
    };

    void refresh( Layer layer ) {
        if (listeners.isEmpty())
            return;
        LabelProviderChangedEvent event = new LabelProviderChangedEvent(this, layer);
        for( ILabelProviderListener listener : listeners ) {
            listener.labelProviderChanged(event);
        }
    }

    /** Cache of images by resource id */
    static ImageCache cache = new ImageCache();

    /**
     * A non null answer when layer has a good label.
     * <p>
     * Where a good/real means:
     * <ul>
     * <li>label.getLabel() != null
     * <li>label.getProperties().getSTring( GENERATED_NAME ) != null
     * </p>
     * <p>
     * This method does not block and used used by the decorateText and our thread to test/acquire
     * the right text. If null is returned the thread will be started in the hopes of producing
     * something.
     * <p>
     *
     * @returns Label for layer, or <code>null</code> if unavailable
     */
    static String label( Layer layer ) {
        String label = layer.getName();
        if (label != null && label.length() > 0){
            return label; // layer has a user supplied name
        }
        label = layer.getProperties().getString(GENERATED_NAME);
        if (label != null) {
            return label; // we have already generated one
        }
        return null;
    }

    /**
     * Generate label.
     * <p>
     * This is used to generate a value for layer.getProperties().getString( GENERATED_NAME ).
     * <p>
     * The generated label from Resource.getInfo().getTitle(). This method will block and should not
     * be called from the event thread.
     * </p>
     *
     * @return gernated layer, or <code>null</code> if none can be determined
     */
    public static String generateLabel( Layer layer ) {
        String name = layer.getName();
        if( name != null ){
            return name; // user supplied name for the win!
        }
        IGeoResource resource = layer.getGeoResource();
        if (resource == null){
            return null;
        }
        String title = resource.getTitle(); // this is from the non-blocking cache!
        if( title == null){
            // fine - no title let us try to connect to find one
            IGeoResourceInfo info = null;
            try {
                info = resource.getInfo(null);
                title = info.getTitle();
                if( title == null ){
                    title = info.getName();
                }
            } catch (IOException e) {
            }
        }
        String layerName = title;
        if( layerName == null ){
            layerName = resource.getID().toBaseFile();
        }
        // Add qualifier if present       
        //String qualifier = resource.getID().getTypeQualifier();
        //if( qualifier != null ){
        //    layerName += "("+qualifier+")";
        //}
        // Side note: Original label, made by item provider uses,
        // resource.getIdentifier() which is non blocking
        //
      if( layer.hasResource(ITransientResolve.class)){
        layerName += " *";
      }
      return layerName;
    }

    /**
     * A non null answer when layer has a good gylph.
     * <p>
     * Where a good/real means:
     * <ul>
     * <li>label.getGylph() != null
     * <li>label.getProperties().get( GENERATED_GYLPH ) != null
     * </p>
     * <p>
     * This method does not block and used used by the decorateImage and our thread to test/acquire
     * the right image. If <code>null</code> is returned the thread will be started in the hopes
     * of producing something.
     * <p>
     *
     * @returns Image for layer, or <code>null</code> if unavailable Image icon( Layer layer ) {
     *          ImageDescriptor glyph = layer.getGlyph(); if (glyph != null) return
     *          cache.getImage(glyph); Image genglyph = (Image)
     *          layer.getProperties().get(GENERATED_ICON); if (genglyph != null &&
     *          !genglyph.isDisposed() ) return genglyph; // we have already generated one return
     *          null; }
     */

    /**
     * Genearte label and place in label.getProperties().getSTring( GENERATED_NAME ).
     * <p>
     * Label is genrated from Resource.
     * </p>
     *
     * @return gernated layer
     */
    public static ImageDescriptor generateIcon( Layer layer ) {
        StyleBlackboard style = layer.getStyleBlackboard();

        if (style != null && !style.getContent().isEmpty()) {
            ImageDescriptor icon = generateStyledIcon(layer);
            if (icon != null)
                return icon;
        }
        ImageDescriptor icon = generateDefaultIcon(layer);
        if (icon != null)
            return icon;
        return null;
    }

    /**
     * Generate icon based on style information.
     * <p>
     * Will return null if an icom based on the current style could not be generated. You may
     * consult generateDefaultIcon( layer ) for a second opionion based on just the layer
     * information.
     *
     * @param layer
     * @return ImageDecriptor for layer, or null in style could not be indicated
     */
    public static ImageDescriptor generateStyledIcon( Layer layer ) {
        StyleBlackboard blackboard = layer.getStyleBlackboard();
        if (blackboard == null)
            return null;

        Style sld = (Style) blackboard.lookup(Style.class); // or
        // blackboard.get(
        // "org.locationtech.udig.style.sld"
        // );
        if (sld != null) {
            Rule rule = getRule(sld);
            return generateStyledIcon(layer, rule);
        }
        if (layer.hasResource(WebMapServer.class)) {
            return null; // do not support styling for wms yet
        }
        return null;
    }

    private static Rule getRule( Style sld ) {
        Rule rule = null;
        int size = 0;

        for( FeatureTypeStyle style : sld.getFeatureTypeStyles() ) {
            for( Rule potentialRule : style.getRules() ) {
                if (potentialRule != null) {
                    Symbolizer[] symbs = potentialRule.getSymbolizers();
                    for( int m = 0; m < symbs.length; m++ ) {
                        if (symbs[m] instanceof PointSymbolizer) {
                            int newSize = SLDs.pointSize((PointSymbolizer) symbs[m]);
                            if (newSize > 16 && size != 0) {
                                // return with previous rule
                                return rule;
                            }
                            size = newSize;
                            rule = potentialRule;
                        } else {
                            return potentialRule;
                        }
                    }
                }
            }
        }
        return rule;
    }

    public static ImageDescriptor generateStyledIcon( ILayer layer, Rule rule ) {
        if (layer.hasResource(FeatureSource.class) && rule != null) {
            SimpleFeatureType type = layer.getSchema();
            GeometryDescriptor geom = type.getGeometryDescriptor();
            if (geom != null) {
                Class geom_type = geom.getType().getBinding();
                if (geom_type == Point.class || geom_type == MultiPoint.class) {
                    return Glyph.point(rule);
                } else if (geom_type == LineString.class || geom_type == MultiLineString.class) {
                    return Glyph.line(rule);
                } else if (geom_type == Polygon.class || geom_type == MultiPolygon.class) {
                    return Glyph.polygon(rule);
                } else if (geom_type == Geometry.class || geom_type == GeometryCollection.class) {
                    return Glyph.geometry(rule);
                }
            }
        }
        IGeoResource resource = layer.findGeoResource(FeatureSource.class);
        if (resource == null)
            return null;
        IGeoResourceInfo info;
        try {
            info = resource.getInfo(null);
        } catch (IOException e) {
            info = null;
        }
        if (info != null) {
            ImageDescriptor infoIcon = info.getImageDescriptor();
            if (infoIcon != null)
                return infoIcon;
        }
        if (resource.canResolve(GridCoverageReader.class)) {
            ImageDescriptor icon = Glyph.grid(null, null, null, null);
            if (icon != null)
                return icon;
        }
        if (resource.canResolve(FeatureSource.class)) {
            ImageDescriptor icon = Glyph.geometry(rule);
            if (icon != null)
                return icon;
        }
        return null;
    }

    /**
     * Generate icon based on simple layer type information without style.
     * <p>
     * The following information is checked:
     * <ul>
     * <li>All WMS resources known to the layer - they often have default icon
     * <li>FeatureSoruce known to the layer - icon can be based on SimpleFeatureType
     * <li>IGeoResourceInfo type information
     * </ul>
     * </p>
     *
     * @param layer
     * @return Icon based on layer, null if unavailable
     */
    static ImageDescriptor generateDefaultIcon( Layer layer ) {
        // check for a WMS layer first as it has a pretty icon
        if (layer.hasResource(WebMapServer.class) && layer.hasResource(ImageDescriptor.class)) {
            try {
                ImageDescriptor legendGraphic = layer.getResource(ImageDescriptor.class, null);
                if (legendGraphic != null)
                    return legendGraphic;
            } catch (IOException notAvailable) {
                // should not really have happened
            }
        }
        // lets try for featuretype based glyph
        //
        if (layer.hasResource(FeatureSource.class)) {

            SimpleFeatureType type = layer.getSchema();
            GeometryDescriptor geom = type.getGeometryDescriptor();
            if (geom != null) {
                Class geom_type = geom.getType().getBinding();
                if (geom_type == Point.class || geom_type == MultiPoint.class) {
                    return Glyph.point(null, null);
                } else if (geom_type == LineString.class || geom_type == MultiLineString.class) {
                    return Glyph.line(null, SLDs.NOTFOUND);
                } else if (geom_type == Polygon.class || geom_type == MultiPolygon.class) {
                    return Glyph.polygon(null, null, SLDs.NOTFOUND);
                } else if (geom_type == Geometry.class || geom_type == GeometryCollection.class) {
                    return Glyph.geometry(null, null);
                } else {
                    return Glyph.geometry(null, null);
                }
            }
        }
       
        //
        // Resource based glyph?
        //
        IGeoResourceInfo info = null;
        try {
            if( !layer.getGeoResources().isEmpty() ){
                info = layer.getGeoResources().get(0).getInfo(null);
            }
        } catch (IOException e) {
            //
        }
        if (info != null) {
            ImageDescriptor infoIcon = info.getImageDescriptor();
            if (infoIcon != null)
                return infoIcon;
        }

        if (layer.hasResource(GridCoverageReader.class)) {
            ImageDescriptor icon = Glyph.grid(null, null, null, null);
            if (icon != null)
                return icon;
        }
        if (layer.hasResource(FeatureSource.class)) {
            ImageDescriptor icon = Glyph.geometry(null, null);
            if (icon != null)
                return icon;
        }
        return null; // default probided by lable provider will have to do
    }

    /**
     * @see org.eclipse.jface.viewers.ILabelDecorator#decorateText(java.lang.String,
     *      java.lang.Object)
     */
    public String decorateText( String text, Object element ) {
        if (!(element instanceof Layer))
            return null;
        Layer layer = (Layer) element;
        try {
            String label = label(layer); // test for label name or generated
            // name

            if (label != null && label.length() != 0)
                return label;

            synchronized (queue) {
                if (!queue.contains(layer)) {
                    queue.add(layer); // thread will wake up and generate us a
                    // layer
                    picasso.schedule();
                }
            }
        } catch (Throwable problem) {
            ProjectUIPlugin.getDefault().getLog().log(
                    new Status(IStatus.WARNING, ProjectUIPlugin.ID, IStatus.INFO,
                            "Generated name unavailable " + layer, problem)); //$NON-NLS-1$
        }
        return null; // use existing default from item provider
    }

    /**
     * We are not allowed to block, test if generation is needed and start up the queue.
     * <p>
     * State Table of Image \ Image Descriptor:
     *
     * <pre><code>
     *            | null         | icon               
     *   ---------+--------------+---------------------+
     *   disposed | queue        | image =             |
     *    or null |   layer      |  icon.createImage() |
     *   ---------+--------------+---------------------+
     *   image    | both         | image               |
     *            +--------------+---------------------+
     * </code></pre>
     *
     * This attempts to reduce the amount of flicker experienced as the layer figures out its glyph
     * in the face of many events.
     * </p>
     * <p>
     * Everyone gives us events - who gives us icons?
     * <ul>
     * <li>If the user has given the layer an icon we don't need to generate anything.
     * <li>piccaso will wait on the queue and generate icons, and refresh the decorator.
     * <li>We will get the refresh and generate an Image from the Icon, we can use this image when
     * we are nexted refreshed.
     * <li>A random eclipse code will dispose our Images, and refrsh us (We can still generate our
     * images from the saved icon).
     * <li>TODO Review : The listener will watch for changes to layer,if any look interesting the icon
     * will be cleared and we will be refreshed. We still have our image so their will be no
     * downtime while waiting for piccaso to make us a new Icon.
     * </ul>
     * So what happens for a layer that we cannot generate a icon for? We will place it in the queue
     * *every* time. Who knows maybe style or something will change and we can do better then the
     * default.
     * </p>
     *
     * @see org.eclipse.jface.viewers.ILabelDecorator#decorateImage(org.eclipse.swt.graphics.Image,
     *      java.lang.Object)
     */
    public Image decorateImage( Image origionalImage, Object element ) {
        if (!(element instanceof Layer))
            return null;

        Layer layer = (Layer) element;
        if (layer.getIcon() != null)
            return null; // don't replace user's glyph

        ImageDescriptor icon = (ImageDescriptor) layer.getProperties().get(GENERATED_ICON);
        if (icon == null) { // we need to generate our icon - check every time
            // it may now be possible
            synchronized (queue) {
                queue.add(layer); // thread will wake up and generate us a
                // layer
                picasso.schedule();
            }
        }
        return null; // next time through the origionalImage will be based on
        // our icon
        /*
         * Image image = (Image) layer.getProperties().get(GENERATED_IMAGE); if( image != null ){
         * if( !image.isDisposed()){ //return image; // we have an image already to go } image =
         * null; // forget this image it is dead layer.getProperties().put( GENERATED_IMAGE, null ); }
         * if( icon != null ){ Image newImage = icon.createImage(); // returns null on error if(
         * newImage != null ){ layer.getProperties().put( GENERATED_IMAGE, newImage ); return
         * newImage; } icon = null; // icon did not work - better clear it and try again.
         * layer.getProperties().put( GENERATED_ICON, null ); } return null; // use default from
         * item provider (often based on GeoResource type)
         */
    }

    /**
     * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener)
     */
    public void addListener( ILabelProviderListener listener ) {
        listeners.add(listener);
    }

    /**
     * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
     */
    public void dispose() {
        picasso.cancel();
        Thread.yield();
        disposed=true;
        queue.clear();

        // clean up
        if (listeners != null) {
            listeners.clear();
            listeners = null;
        }
        if (cache != null) {
            cache.dispose();
            cache = null;
        }
    }

    /**
     * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object,
     *      java.lang.String)
     */
    public boolean isLabelProperty( Object element, String property ) {
        return true;
        /*
         * return "glyph".equals( property ) || "styleBlackboard".equals( property ) ||
         * "name".equals( property ) || "geoResources".equals( property );
         */
    }

    /**
     * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener)
     */
    public void removeListener( ILabelProviderListener listener ) {
        listeners.remove(listener);
    }

}
TOP

Related Classes of org.locationtech.udig.project.ui.internal.LayerGeneratedGlyphDecorator

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.