org.polymap.core.mapeditor.RenderManager.java Source code

Java tutorial

Introduction

Here is the source code for org.polymap.core.mapeditor.RenderManager.java

Source

/* 
 * polymap.org
 * Copyright 2009-2013, Polymap GmbH. 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 2.1 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.core.mapeditor;

import static org.polymap.core.project.ILayer.PROP_GEORESID;
import static org.polymap.core.project.ILayer.PROP_OPACITY;
import static org.polymap.core.project.ILayer.PROP_ORDERKEY;
import static org.polymap.core.project.ILayer.PROP_STYLE;
import static org.polymap.core.project.ILayer.PROP_TILESIZE;

import java.util.EventObject;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import java.beans.PropertyChangeEvent;
import net.refractions.udig.catalog.IGeoResource;
import net.refractions.udig.catalog.IService;

import org.geotools.geometry.jts.ReferencedEnvelope;
import org.json.JSONObject;
import org.osgi.service.http.NamespaceException;

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

import com.google.common.collect.Iterables;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;

import org.eclipse.rwt.RWT;
import org.eclipse.rwt.service.ISettingStore;

import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.core.runtime.IProgressMonitor;

import org.polymap.core.CorePlugin;
import org.polymap.core.data.FeatureChangeEvent;
import org.polymap.core.mapeditor.services.SimpleWmsServer;
import org.polymap.core.model.event.IModelChangeListener;
import org.polymap.core.model.event.ModelChangeEvent;
import org.polymap.core.operation.OperationSupport;
import org.polymap.core.project.ILayer;
import org.polymap.core.project.IMap;
import org.polymap.core.project.PipelineHolder;
import org.polymap.core.project.ProjectRepository;
import org.polymap.core.project.Visible;
import org.polymap.core.project.model.LayerComposite;
import org.polymap.core.project.operations.OpenMapOperation;
import org.polymap.core.qi4j.event.PropertyChangeSupport;
import org.polymap.core.runtime.IMessages;
import org.polymap.core.runtime.UIJob;
import org.polymap.core.runtime.event.EventFilter;
import org.polymap.core.runtime.event.EventHandler;
import org.polymap.core.runtime.event.EventManager;
import org.polymap.core.workbench.PolymapWorkbench;

import org.polymap.service.ServicesPlugin;
import org.polymap.service.http.MapHttpServer;

/**
 * The RenderManager is the bridge between an {@link IMap} and the {@link MapEditor}
 * that displays the contents of this map. It listens to all kind of events regarding
 * its map and changes its {@link #mapEditor} as needed.
 * 
 * @author <a href="http://www.polymap.de">Falko Brutigam</a>
 * @since 3.0
 */
public class RenderManager {

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

    private static final IMessages i18n = Messages.forPrefix("RenderManager");

    private IMap map;

    private MapEditor mapEditor;

    private MapHttpServer wmsServer;

    /** Sorted set, see {@link RenderLayerDescriptor#compare()}. */
    private TreeSet<RenderLayerDescriptor> descriptors = new TreeSet();

    private MapDomainListener modelListener = new MapDomainListener();

    private FeatureListener featureListener = new FeatureListener();

    public RenderManager(IMap map, MapEditor mapEditor) {
        super();
        this.map = map;
        this.mapEditor = mapEditor;

        // model listener
        EventFilter eventFilter = new EventFilter<EventObject>() {
            public boolean apply(EventObject ev) {
                if (RenderManager.this.map == null || ev.getSource() == null) {
                    return false;
                }
                if (ev.getSource() instanceof IMap) {
                    IMap eventMap = (IMap) ev.getSource();
                    return RenderManager.this.map.equals(eventMap);
                } else if (ev.getSource() instanceof ILayer) {
                    ILayer layer = (ILayer) ev.getSource();
                    return RenderManager.this.map.equals(layer.getMap());
                }
                return false;
            }
        };
        ProjectRepository module = ProjectRepository.instance();
        module.addEntityListener(modelListener, eventFilter);

        // feature listener
        EventManager em = EventManager.instance();
        em.subscribe(featureListener, new EventFilter<FeatureChangeEvent>() {
            public boolean apply(FeatureChangeEvent ev) {
                return RenderManager.this.map != null && ev.getSource() != null
                        && RenderManager.this.map.equals(ev.getSource().getMap());
            }
        });
    }

    public void dispose() {
        clearPipelines();
        if (modelListener != null && map != null) {
            ProjectRepository module = ProjectRepository.instance();
            module.removeEntityListener(modelListener);
            modelListener = null;
        }
        if (featureListener != null) {
            EventManager.instance().unsubscribe(featureListener);
            featureListener = null;
        }
        if (wmsServer != null) {
            destroyWms(wmsServer);
            wmsServer = null;
        }
        this.map = null;
        this.mapEditor = null;
    }

    protected synchronized void clearPipelines() {
        if (mapEditor != null) {
            for (RenderLayerDescriptor descriptor : descriptors) {
                mapEditor.removeLayer(descriptor);
            }
        }
        descriptors.clear();
    }

    /**
     * Prepares the render pipelines / OwsServers for the map.
     * <ol>
     * <li>find the {@link IService}s that are associated with the layers of the {@link #map}.</li>
     * <li>create a render pipeline for each service.<li>
     * <li>create a {@link WmsService} server for each service/pipeline.</li>
     * </ol>
     * @throws Exception 
     */
    protected synchronized void updatePipelines() {
        clearPipelines();
        IProgressMonitor monitor = UIJob.monitorForThread();

        if (wmsServer != null) {
            destroyWms(wmsServer);
            wmsServer = null;
        }
        wmsServer = createWms();

        // check geoResource of layers
        for (final ILayer layer : map.getLayers()) {
            try {
                log.debug("layer: " + layer + ", label= " + layer.getLabel() + ", visible= " + layer.isVisible());
                if (layer.isVisible()) {
                    monitor.subTask(i18n.get("georesTaskTitle", layer.getLabel()));
                    IGeoResource res = layer.getGeoResource();
                    if (res == null) {
                        log.warn("Unable to find geo resource of layer: " + layer);
                        Display.getCurrent().asyncExec(new Runnable() {
                            public void run() {
                                IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
                                MessageBox box = new MessageBox(window.getShell(),
                                        SWT.APPLICATION_MODAL | SWT.ICON_WARNING);
                                String label = layer instanceof LayerComposite
                                        ? ((LayerComposite) layer).georesId().get()
                                        : layer.getLabel();
                                box.setMessage(i18n.get("noGeoresMsg", label));
                                box.setText(i18n.get("noGeoresTitle", label));
                                box.open();
                            }
                        });
                        continue;
                    }

                    RenderLayerDescriptor descriptor = new RenderLayerDescriptor(
                            StringUtils.removeStart(wmsServer.getPathSpec(), "/"), layer.getOrderKey(),
                            layer.getOpacity());
                    descriptor.layer = layer;
                    boolean added = descriptors.add(descriptor);
                    assert added : "Unable to add layer descriptor: " + descriptor.layerNames();

                    monitor.worked(1);
                }
            } catch (Exception e) {
                // XXX mark layers!?
                log.warn("skipping layer: " + layer.getLabel() + " (" + e.toString(), e);
            }
        }

        // add layers to mapEditor
        for (RenderLayerDescriptor descriptor : descriptors) {
            mapEditor.addLayer(descriptor);
        }
    }

    protected SimpleWmsServer createWms() {
        try {
            ISettingStore settingStore = RWT.getSettingStore();
            String wmsNamesAttr = settingStore.getAttribute("RenderManager.wmsnames");
            JSONObject wmsNames = wmsNamesAttr != null ? new JSONObject(wmsNamesAttr) : new JSONObject();

            String pathSpec = wmsNames.optString(map.id(), null);
            if (pathSpec == null) {
                pathSpec = ServicesPlugin.createServicePath(map.getLabel() + "--" + System.currentTimeMillis());
                wmsNames.put(map.id(), pathSpec);
                settingStore.setAttribute("RenderManager.wmsnames", wmsNames.toString());
            }

            log.info("Service path: " + pathSpec);

            // use our own WMS impl
            SimpleWmsServer result = new SimpleWmsServer();
            result.init(map);

            try {
                CorePlugin.registerServlet(pathSpec, result, null);
            }
            // session logged out without closing all services 
            catch (NamespaceException e) {
                CorePlugin.unregister(pathSpec);
                CorePlugin.registerServlet(pathSpec, result, null);
            }

            log.debug("    URL: " + result.getPathSpec());
            return result;
        } catch (Exception e) {
            PolymapWorkbench.handleError(MapEditorPlugin.PLUGIN_ID, this, "Fehler beim Starten des WMS-Services.",
                    e);
            throw new RuntimeException(e);
        }
    }

    protected void destroyWms(MapHttpServer server) {
        CorePlugin.unregister(server);
    }

    /**
     * 
     */
    class FeatureListener {

        @EventHandler(delay = 750, display = true)
        public void featureChanges(List<FeatureChangeEvent> events) {
            Set<ILayer> dirty = new HashSet(events.size());
            for (FeatureChangeEvent ev : events) {
                dirty.add(ev.getSource());
            }
            for (ILayer layer : dirty) {
                RenderLayerDescriptor descriptor = findDescriptorForLayer(layer);
                if (descriptor != null) {
                    mapEditor.reloadLayer(descriptor);
                }
            }
        }
    }

    /**
     * 
     */
    class MapDomainListener implements IModelChangeListener {

        /**
         * Close the corresponding {@link MapEditor}. This call triggers {@link MapEditor#dispose()}
         * and then {@link RenderManager#dispose()}. So after this call <code>map</code> is null.
         * @return
         */
        protected IWorkbenchPage closeMapEditor() {
            IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
            IWorkbenchPage page = window.getActivePage();

            page.closeEditor(mapEditor, false);
            return page;
        }

        public void modelChanged(ModelChangeEvent ev) {
            // check CRS changes after an operation has has finished and
            // extends are transformed in new CRS
            Iterable<PropertyChangeEvent> crsEvents = ev.events(new EventFilter<PropertyChangeEvent>() {
                public boolean apply(PropertyChangeEvent iev) {
                    return iev.getSource() instanceof IMap
                            && IMap.PROP_CRSCODE.equalsIgnoreCase(iev.getPropertyName());
                }
            });
            if (!Iterables.isEmpty(crsEvents)) {
                try {
                    // map is null after MapEditor closed and dispose()
                    IMap savedMap = map;
                    IWorkbenchPage page = closeMapEditor();

                    // re-open editor
                    OpenMapOperation op = new OpenMapOperation(savedMap, page);
                    OperationSupport.instance().execute(op, true, true);
                } catch (Exception e) {
                    PolymapWorkbench.handleError(MapEditorPlugin.PLUGIN_ID, this, "", e);
                }

                //                        MessageDialog.openInformation( PolymapWorkbench.getShellToParentOn(),
                //                                Messages.get( "RenderManager_updateCrs_title" ),
                //                                Messages.get( "RenderManager_updateCrs_msg" ) );
                //                        //mapEditor.updateMapCRS();
            }
        }

        /*
         * The delay collects events.
         * 
         * Besides the delay allows ImageCacheProcessor (for example) to disable
         * *before* the layer is reloaded.
         */
        @EventHandler(delay = 500, display = true)
        public void propertyChange(List<PropertyChangeEvent> events) {
            boolean updatePipelines = false;

            for (PropertyChangeEvent ev : events) {
                // ILayer
                if (ev.getSource() instanceof ILayer) {
                    ILayer layer = (ILayer) ev.getSource();
                    RenderLayerDescriptor descriptor = findDescriptorForLayer(layer);

                    if ("visible".equals(ev.getPropertyName())) {
                        updatePipelines = true;
                    } else if ("edit".equals(ev.getPropertyName())) {
                        if (descriptor != null) {
                            mapEditor.reloadLayer(descriptor);
                        }
                        //                        updatePipelines = true;
                    } else if (PROP_OPACITY.equals(ev.getPropertyName())) {
                        if (descriptor != null && descriptor.layer != null) {
                            mapEditor.setLayerOpacity(descriptor, layer.getOpacity());
                        } else {
                            updatePipelines = true;
                        }
                    } else if (PROP_TILESIZE.equals(ev.getPropertyName())) {
                        updatePipelines = true;
                    } else if (PROP_ORDERKEY.equals(ev.getPropertyName())) {
                        if (descriptor != null && descriptor.layer != null) {
                            mapEditor.setLayerZPriority(descriptor, layer.getOrderKey());
                        } else {
                            updatePipelines = true;
                        }
                    } else if (PROP_STYLE.equals(ev.getPropertyName())) {
                        if (descriptor != null) {
                            mapEditor.reloadLayer(descriptor);
                        }
                    } else if (PipelineHolder.PROP_PROCS.equals(ev.getPropertyName())) {
                        if (descriptor != null) {
                            updatePipelines = true;
                        }
                    } else if (PROP_GEORESID.equals(ev.getPropertyName())) {
                        if (descriptor != null) {
                            mapEditor.reloadLayer(descriptor);
                        }
                    } else if (Visible.PROP_RERENDER.equals(ev.getPropertyName())) {
                        if (descriptor != null) {
                            mapEditor.reloadLayer(descriptor);
                        }
                    }
                }
                // IMap
                else if (ev.getSource() instanceof IMap) {
                    // check if map was deleted
                    if (PropertyChangeSupport.PROP_ENTITY_REMOVED.equals(ev.getPropertyName())) {
                        closeMapEditor();
                    } else if (IMap.PROP_LAYERS.equals(ev.getPropertyName())) {
                        updatePipelines = true;
                    } else if (IMap.PROP_EXTENT.equals(ev.getPropertyName())) {
                        ReferencedEnvelope extent = (ReferencedEnvelope) ev.getNewValue();
                        mapEditor.setMapExtent(extent);

                        // XXX refactor this out to MapEditor so that it can be used elsewhere 
                        IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
                        IWorkbenchPage page = window.getActivePage();
                        if (page != null && mapEditor != null
                                && page.findEditor(mapEditor.getEditorInput()) != null) {
                            page.activate(mapEditor);
                        }
                    } else if (IMap.PROP_MAXEXTENT.equals(ev.getPropertyName())) {
                        mapEditor.setMapExtent(map.getMaxExtent());
                        mapEditor.setMaxExtent(map.getMaxExtent());
                    } else if (IMap.PROP_CRSCODE.equalsIgnoreCase(ev.getPropertyName())) {
                        // stop listening to events as extents and CRS do not match any longer;
                        // wait for ModelChangeEvent to reload
                        ProjectRepository module = ProjectRepository.instance();
                        // FIXME
                        log.warn("!!! commented out: module.removePropertyChangeListener( modelListener );");
                    }
                }
            }
            if (updatePipelines) {
                updatePipelines();
            }
        }
    }

    private final RenderLayerDescriptor findDescriptorForLayer(ILayer layer) {
        for (RenderLayerDescriptor descriptor : descriptors) {
            if (layer.equals(descriptor.layer)) {
                return descriptor;
            }
        }
        return null;
    }

    /**
     * A set of {@link ILayer}s along with render properties to be displayed by a
     * {@link MapEditor}.
     */
    public class RenderLayerDescriptor implements Comparable {

        int zPriority;

        int opacity;

        ILayer layer;

        String servicePath;

        RenderLayerDescriptor(String servicePath, int zPriority, int opacity) {
            this.servicePath = servicePath;
            this.zPriority = zPriority;
            this.opacity = opacity;
        }

        String title() {
            return layerNames();
        }

        String layerNames() {
            return layer != null ? layer.getLabel() : "";
        }

        /**
         * Sort layers after zPriority. Fixes ticket
         * http://polymap.org/atlas/ticket/185.
         */
        @Override
        public int compareTo(Object o) {
            RenderLayerDescriptor other = (RenderLayerDescriptor) o;
            if (zPriority != other.zPriority) {
                // reverse order: add high zPriority to map first
                return zPriority - other.zPriority;
            } else {
                return hashCode() - other.hashCode();
            }
        }

    }

}