org.integratedmodelling.aries.webapp.view.ScenarioEditor.java Source code

Java tutorial

Introduction

Here is the source code for org.integratedmodelling.aries.webapp.view.ScenarioEditor.java

Source

/*
Copyright 2011 The ARIES Consortium (http://www.ariesonline.org)
    
This file is part of ARIES.
    
ARIES is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
    
ARIES 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
General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with ARIES.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.integratedmodelling.aries.webapp.view;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;

import org.apache.commons.lang.StringUtils;
import org.geotools.geometry.jts.JTS;
import org.integratedmodelling.aries.webapp.ARIESWebappPlugin;
import org.integratedmodelling.aries.webapp.visualization.WebZKVisualization;
import org.integratedmodelling.corescience.interfaces.IContext;
import org.integratedmodelling.corescience.interfaces.IState;
import org.integratedmodelling.corescience.metadata.Metadata;
import org.integratedmodelling.geospace.extents.ArealExtent;
import org.integratedmodelling.geospace.literals.ShapeValue;
import org.integratedmodelling.modelling.ModellingPlugin;
import org.integratedmodelling.modelling.context.FilteredTransformation;
import org.integratedmodelling.modelling.interfaces.IModel;
import org.integratedmodelling.modelling.model.Model;
import org.integratedmodelling.modelling.model.ModelFactory;
import org.integratedmodelling.modelling.model.Scenario;
import org.integratedmodelling.modelling.storyline.ModelStoryline;
import org.integratedmodelling.modelling.storyline.Storyline;
import org.integratedmodelling.modelling.visualization.VisualizationFactory;
import org.integratedmodelling.modelling.visualization.storyline.StorylineTemplate;
import org.integratedmodelling.olmaps.OLmaps;
import org.integratedmodelling.olmaps.event.FeatureAddedEvent;
import org.integratedmodelling.olmaps.layer.GoogleMapsLayer;
import org.integratedmodelling.olmaps.layer.VectorLayer;
import org.integratedmodelling.thinklab.exception.ThinklabException;
import org.integratedmodelling.thinklab.exception.ThinklabRuntimeException;
import org.integratedmodelling.thinklab.http.application.ThinklabWebApplication;
import org.integratedmodelling.thinklab.http.geospace.zk.OLMAPS;
import org.integratedmodelling.thinklab.interfaces.knowledge.IConcept;
import org.integratedmodelling.thinklab.literals.BooleanValue;
import org.integratedmodelling.thinklab.webapp.ZK;
import org.integratedmodelling.thinklab.webapp.ZK.ZKComponent;
import org.integratedmodelling.thinklab.webapp.view.components.Ribbon;
import org.integratedmodelling.thinklab.webapp.view.components.ThinkcapComponent;
import org.integratedmodelling.utils.NameGenerator;
import org.integratedmodelling.utils.Pair;
import org.integratedmodelling.utils.Path;
import org.integratedmodelling.utils.Triple;
import org.integratedmodelling.utils.image.ColorMap;
import org.opengis.referencing.operation.TransformException;
import org.zkoss.zhtml.Text;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zul.Button;
import org.zkoss.zul.Div;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.Toolbarbutton;

import com.vividsolutions.jts.geom.Coordinate;

/**
 * Scenario editor. 
 * 
 * TODO: make it a bit less aries-dependent; use Thinkcap component for area selection (tie to context).
 * 
 * @author Ferdinando
 *
 */
public class ScenarioEditor extends ThinkcapComponent {

    private static final long serialVersionUID = 8048071165550994145L;
    private int _height;
    private int _width;
    AriesBrowser _browser;
    private int leftW;
    private int rightW;
    private int topH;
    private int botH;
    private int midH;

    private int mapState = 0;
    private IModel model = null;
    private Scenario scenario;

    Toolbarbutton scaddpolyg = null;
    Toolbarbutton scsubpolyg = null;
    Toolbarbutton scresetdraw = null;
    Textbox scname = null;

    private OLmaps layermap;
    private GoogleMapsLayer scgoogle;
    private VectorLayer scvector;
    private int picmode;
    private ShapeValue roi = null;
    private IContext context;
    private ModelStoryline storyline;
    private ArrayList<IConcept> editables = new ArrayList<IConcept>();
    private Collection<Scenario> scenarios = new ArrayList<Scenario>();

    private Div layereditor;
    private Ribbon layerselector;

    class Transformation {
        ShapeValue roi = null;
        IConcept obs = null;
        Object[] sel = null;
        Object ret = null;
    }

    ArrayList<Transformation> transformations = new ArrayList<ScenarioEditor.Transformation>();
    private String scenarioId;
    private Pair<Integer, Integer> xy = null;

    public ScenarioEditor(AriesBrowser browser, int width, int height) {

        this._width = width;
        this._height = height;
        this._browser = browser;
        this.setSclass(STYLE.SCENARIO_WINDOW);
        this.setHeight(_height + "px");
        this.setWidth(_width + "px");

        // relative window sizes
        this.midH = height - 100;
        this.leftW = _width * 23 / 100;
        this.rightW = _width * 73 / 100;
        this.topH = (midH * 33 / 100);
        this.botH = (midH * 63 / 100) + 9;
    }

    @Override
    public void initialize() {
    }

    /**
     * Recover the scenario created
     * @return
     * @throws ThinklabException
     */
    public Triple<String, Scenario, IContext> getScenarioContext() throws ThinklabException {

        IContext ret = context.cloneExtents();
        for (Transformation t : transformations) {

            // create and insert transformations into context
            FilteredTransformation tr = new FilteredTransformation(t.obs, t.ret);
            if (t.sel != null) {
                for (Object o : t.sel)
                    tr.addFilter(o);
            }
            //         ((ObservationContext)ret).addTransformation(tr);

        }
        return new Triple<String, Scenario, IContext>(this.scenarioId, this.scenario, ret);
    }

    public void setStoryline(ModelStoryline storyline) throws ThinklabException {

        if (storyline.getStatus() == Storyline.COMPUTED) {

            this.context = storyline.getContext();
            this.storyline = storyline;

            /*
             * compute possible scenarios and list of editable
             * concepts
             */
            this.editables = new ArrayList<IConcept>();
            for (IState s : this.context.getStates()) {
                String ed = s.getObservableClass().getAnnotation(ModellingPlugin.EDITABLE_ANNOTATION);
                if (ed != null && BooleanValue.parseBoolean(ed)) {
                    this.editables.add(s.getObservableClass());
                }
            }

            this.scenarios = ModelFactory.get().getApplicableScenarios((Model) storyline.getModel(), context, true);

        }

        setup();
    }

    public void setup() {

        clear();

        setContent(ZK.vbox(

                ZK.separator(false).height(8),

                // top bar
                ZK.hbox(ZK.div(ZK.label("Scenario editor").sclass(STYLE.TEXT_LARGE_BOLD_RED)).align("left"),
                        ZK.div(ZK.hbox(ZK.label("Scenario name: "), ZK.textbox().width(200).id("scname")))
                                .width("100%").align("right"))
                        .width("100%"),

                // main area
                ZK.window(ZK.hbox(ZK.vbox(
                        ZK.groupbox("Global scenarios", createPredefinedEditor(model)).width(leftW).height(topH),
                        ZK.groupbox("Editable parameters", createParameterEditor()).width(leftW).height(botH)),
                        ZK.groupbox("Policy options editor",
                                ZK.vbox(createSpaceSelector(), createLayerSelector(), createLayerEditor().hide()))
                                .width(rightW).height(midH))),

                // bottom bar
                ZK.div(ZK.hbox(ZK.button("Save").listener("onClick", new EventListener() {
                    @Override
                    public void onEvent(Event arg0) throws Exception {
                        String sname = scname.getValue();
                        if (sname == null || sname.equals("")) {
                            alert("Please enter a name for the scenario");
                        } else {
                            _browser.addScenario(context);
                        }
                    }
                }), ZK.button("Cancel").listener("onClick", new EventListener() {
                    @Override
                    public void onEvent(Event arg0) throws Exception {
                        _browser.addScenario(null);
                    }
                }))).align("right")).width("100%"));

        afterCompose();

    }

    public void setLocation(int zoom, ArealExtent iExtent) {

        Coordinate p1 = null, p2 = null;
        try {
            p1 = JTS.transform(new Coordinate(iExtent.getWest(), iExtent.getSouth()), null,
                    ARIESWebappPlugin.get().geoToGoogleTransform);
            p2 = JTS.transform(new Coordinate(iExtent.getEast(), iExtent.getNorth()), null,
                    ARIESWebappPlugin.get().geoToGoogleTransform);
        } catch (TransformException e) {
            throw new ThinklabRuntimeException(e);
        }

        layermap.setZoom(zoom);
        layermap.setCenter(p1.x + (p2.x - p1.x) / 2, p1.y + (p2.y - p1.y) / 2);
    }

    protected void mergeScenario(Scenario rid) {
        this.scenario.merge(rid);
        // TODO redisplay
    }

    private ZKComponent createLayerSelector() {

        if (this.xy == null) {
            this.xy = VisualizationFactory.get().getPlotSize(58, 58, storyline.getContext());
        }

        ArrayList<ZK.ZKComponent> components = new ArrayList<ZK.ZKComponent>();
        WebZKVisualization visualization = (WebZKVisualization) storyline.getVisualization();

        for (final IConcept c : editables) {

            StorylineTemplate.Page p = storyline.getTemplate().getPage(c);
            if (p == null)
                continue;

            String img = visualization.getStateUrl(p.getConcept(), VisualizationFactory.PLOT_GEOSURFACE_2D);
            if (img != null) {
                components.add(ZK.div(ZK.vbox(ZK.image(img).width(xy.getFirst()).height(xy.getSecond())
                        .listener("onClick", new EventListener() {

                            @Override
                            public void onEvent(Event arg0) throws Exception {
                                // setup value chooser and filter editor
                            }
                        }),
                        ZK.label(StringUtils.abbreviate(p.getName(), 24)).sclass(STYLE.TEXT_VERYSMALL)
                                .align("center").fillx()))
                        .align("center").width(120).tooltip(p.getRunningHead()).hand()
                        .listener("onClick", new EventListener() {

                            @Override
                            public void onEvent(Event arg0) throws Exception {
                                editLayer(c);
                            }
                        }).tmargin(64 - xy.getSecond()));
            }
        }

        return ZK.ribbon(rightW - 6, 64, 48, components.toArray(new ZK.ZKComponent[components.size()]))
                .id("layerselector");

    }

    protected void editLayer(IConcept c) throws ThinklabException {
        // TODO Auto-generated method stub      
        IState state = context.getState(c);
        StorylineTemplate.Page page = storyline.getTemplate().getPage(c);
        String img = ((WebZKVisualization) (storyline.getVisualization())).getStateUrl(page.getConcept(),
                VisualizationFactory.PLOT_GEOSURFACE_2D);

        Boolean continuous = (Boolean) state.getMetadata().get(Metadata.CONTINUOUS);
        HashMap<IConcept, Integer> ranks = Metadata.getClassMappings(state.getMetadata());

        ZK.ZKComponent selector = null;
        int rwid = rightW - 6 - 328;

        if (continuous != null && continuous) {

            /*
             * class selector for continuous
             * Image w/labels 
             * Separator
             * Range selector <All><Range><Greater than><Lower than><Equal to>
             *       Set To
             *       <value> (show range)
             * Separator
             * In <Chosen poly><Area>
             * Separator
             * <OK><Cancel>
             */
            selector = ZK.hbox();

        } else if (ranks != null) {

            ColorMap cmap = (ColorMap) state.getMetadata().get(Metadata.COLORMAP);
            HashMap<IConcept, String> legend = VisualizationFactory.get().getClassLegend(state);

            /*
             * create class selector for layer editor
             * Image (w/ labels)
             * Separator
             * Range Selector <All><Only xxx - multiple selection>
             * Set to: all classes (ribbon) - check out Legend for code
             * Separator
             * In: <chosen polygon><whole area> enable/disable according to selection
             * Separator
             * <ok> <cancel>
             */
            selector = ZK.hbox(ZK.separator(true),
                    ZK.div(ZK.image(img).width(xy.getFirst()).height(xy.getSecond())).width(64).align("center"),
                    ZK.separator(true),
                    // ribbon 
                    ZK.div().width(rwid), ZK.separator(true),
                    // logical filter
                    ZK.div().width(128), ZK.separator(true),
                    // space filter
                    ZK.div().width(82),
                    ZK.vbox(ZK.separator().height(8), ZK.button("  OK  ").listener("onClick", new EventListener() {

                        @Override
                        public void onEvent(Event arg0) throws Exception {
                            /*
                             * collect transformation data from fields
                             */
                            layereditor.setVisible(false);
                            layerselector.setVisible(true);
                        }
                    }).width(46), ZK.button("Cancel").listener("onClick", new EventListener() {

                        @Override
                        public void onEvent(Event arg0) throws Exception {
                            layereditor.setVisible(false);
                            layerselector.setVisible(true);
                        }
                    }).width(46)).width(48), ZK.separator(true));

        } else {
            // WTF? Probably unnecessary - scenarios on categorizations or probabilities?
        }

        ZK.resetComponent(layereditor);
        layereditor.appendChild(selector.get());

        layerselector.setVisible(false);
        layereditor.setVisible(true);

    }

    private ZKComponent createLayerEditor() {
        return ZK.div().id("layereditor");
    }

    private ZKComponent createSpaceSelector() {

        ThinklabWebApplication application = _browser.application;

        return ZK.vbox(ZK.hbox(ZK.div().align("left"), ZK.div(ZK.hbox(ZK.imagebutton("/aries/images/icons/edit.png")
                .id("scaddpolyg").listener("onClick", new EventListener() {
                    @Override
                    public void onEvent(Event arg0) throws Exception {
                        picmode = AriesBrowser.ADD;
                        scvector.setEditControl("polygon");
                        painting(false);
                    }
                }).tooltip("Draw a shape and add it to the selection"),
                ZK.imagebutton("/aries/images/icons/cut_disabled.png").enable(false).id("scsubpolyg")
                        .listener("onClick", new EventListener() {
                            @Override
                            public void onEvent(Event arg0) throws Exception {
                                picmode = AriesBrowser.SUBTRACT;
                                scvector.setEditControl("polygon");
                                painting(true);
                            }
                        }).tooltip("Draw a shape and subtract it from the selection"),
                ZK.imagebutton("/aries/images/icons/delete_disabled.png").enable(false).id("scresetdraw")
                        .listener("onClick", new EventListener() {
                            @Override
                            public void onEvent(Event arg0) throws Exception {
                                picmode = AriesBrowser.IDLE;
                                scvector.clearFeatures();
                                roi = null;
                                idle(false);
                            }
                        }).tooltip("Clear all selections made so far"),
                ZK.image("/aries/images/icons/separator.png"),
                ZK.imagebutton("/aries/images/icons/world.png").listener("onClick", new EventListener() {
                    @Override
                    public void onEvent(Event arg0) throws Exception {
                        mapState = (mapState + 1) % 4;
                        scgoogle.setMapType(AriesBrowser.mapStates[mapState]);
                    }
                }).tooltip("Cycle through map views")))).align("right").fillx(),
                OLMAPS.map(OLMAPS.googlelayer().maptype("physical").id("scgoogle"),
                        OLMAPS.vectorlayer().drawcontrols(true).id("scvector")).zoom(2).id("layermap")
                        .width(rightW - 6).height(midH - 120));
    }

    private ZKComponent createParameterEditor() {

        // TODO obviously need to be real, not hard-coded, when I finished
        // demonstrating.
        return ZK.vbox(ZK.separator(false).height(8), ZK.label("Sequestration relevance threshold"),
                ZK.hbox(ZK.label("0"), ZK.slider().width(180), ZK.label("100 tons C/ha/yr")),
                ZK.separator(true).height(8), ZK.label("Use relevance threshold"),
                ZK.hbox(ZK.label("0"), ZK.slider().width(180), ZK.label("100 tons C/ha/yr")),
                ZK.separator(true).height(8), ZK.label("Sink relevance threshold"),
                ZK.hbox(ZK.label("0"), ZK.slider().width(180), ZK.label("100 tons C/ha/yr")), ZK.separator(true));

    }

    private ZK.ZKComponent createPredefinedEditor(IModel model) {

        ZK.ZKComponent ret = null;

        if (scenarios.size() > 0) {

            String desc = null;
            String label = null;

            ZK.ZKComponent[] lst = new ZK.ZKComponent[scenarios.size()];
            int i = 0;
            for (Scenario s : scenarios) {
                if (i == 0) {
                    desc = s.getDescription();
                    label = Path.getLast(s.getId()).toUpperCase().replaceAll("-", " ");
                }
                lst[i++] = ZK.listitem(Path.getLast(s.getId()).toUpperCase().replaceAll("-", " "), s);
            }

            if (desc == null || desc.equals("")) {
                desc = "No description was given for this scenario.";
            }

            ret = ZK.vbox(ZK.listbox(lst).nrows(1).selected(0).mold("select").id("pdef_chooser")
                    .listener("onSelect", new EventListener() {
                        @Override
                        public void onEvent(Event event) throws Exception {
                            Listitem li = (Listitem) ((SelectEvent) event).getSelectedItems().iterator().next();
                            Scenario rid = (Scenario) li.getValue();
                            ((Text) (get("pdef_desc").getFirstChild())).setValue(rid.getDescription());
                            ((Button) get("pdef_merge")).setLabel(
                                    "Merge " + Path.getLast(rid.getName()).toUpperCase().replaceAll("-", " "));

                        }
                    }).width(leftW - 4), ZK.text(desc).id("pdef_desc").width(leftW - 4).height(topH - 60),
                    ZK.button("Merge " + label).id("pdef_merge").width(leftW - 4).listener("onClick",
                            new EventListener() {

                                @Override
                                public void onEvent(Event arg0) throws Exception {
                                    Listitem li = (Listitem) ((Listbox) get("pdef_chooser")).getSelectedItems()
                                            .iterator().next();
                                    mergeScenario((Scenario) li.getValue());
                                }
                            }));

        } else {
            ret = ZK.label("No applicable scenarios");
        }
        return ret;
    }

    public Scenario getScenario() {
        return scenario;
    }

    public void onFeatureAdded$scvector(Event e) {

        boolean userDrawn = (this.picmode == AriesBrowser.ADD || this.picmode == AriesBrowser.SUBTRACT);
        FeatureAddedEvent ev = (FeatureAddedEvent) e;

        if (userDrawn) {
            scvector.setEditControl("navigate");
        }

        try {

            boolean redraw = false;
            ShapeValue shape = new ShapeValue(layermap.getProjectionId() + " " + ev.getFeatureWKT());

            /*
             * happens when user draws too fast, especially with slow
             * browsers
             */
            if (!shape.isValid()) {
                if (getRegionOfInterest() == null)
                    scvector.clearFeatures();
                return;
            }

            if (this.picmode == AriesBrowser.ADD) {
                redraw = addRegionOfInterest(shape);
            } else if (this.picmode == AriesBrowser.SUBTRACT) {
                redraw = true;
                subtractRegionOfInterest(shape);
            }

            this.picmode = AriesBrowser.IDLE;

            if (redraw) {
                scvector.clearFeatures();
                scvector.addFeature(NameGenerator.newName("scsh"), getRegionOfInterest().getWKT());
            }

        } catch (ThinklabException e1) {
            throw new ThinklabRuntimeException(e1);
        }

        if (userDrawn) {
            idle(roi != null);
        }
    }

    public ShapeValue getRegionOfInterest() throws ThinklabException {
        return roi;
    }

    public void subtractRegionOfInterest(ShapeValue region) throws ThinklabException {

        if (roi != null)
            roi = roi.difference(region);
    }

    public boolean addRegionOfInterest(ShapeValue region) throws ThinklabException {
        boolean ret = roi != null;
        roi = roi == null ? region : roi.union(region);
        return ret;
    }

    public void resetRegionOfInterest(ShapeValue region) throws ThinklabException {
        roi = region;
    }

    // set the icons in "painting" mode
    void painting(boolean isScissors) {

        scaddpolyg.setImage(isScissors ? ZK.fixUrl("/aries/images/icons/edit.png")
                : ZK.fixUrl("/aries/images/icons/edit_active.png"));
        scsubpolyg.setImage(isScissors ? ZK.fixUrl("/aries/images/icons/cut_active.png")
                : ZK.fixUrl("/aries/images/icons/cut_disabled.png"));

        scresetdraw.setImage(ZK.fixUrl("/images/icons/cancel_disabled.png"));
    }

    void idle(boolean hasSelection) {

        scaddpolyg.setImage(ZK.fixUrl("/aries/images/icons/edit.png"));
        scsubpolyg.setImage(hasSelection ? ZK.fixUrl("/aries/images/icons/cut.png")
                : ZK.fixUrl("/aries/images/icons/cut_disabled.png"));
        scresetdraw.setImage(hasSelection ? ZK.fixUrl("/aries/images/icons/delete.png")
                : ZK.fixUrl("/aries/images/icons/delete_disabled.png"));

        scsubpolyg.setDisabled(!hasSelection);
        scresetdraw.setDisabled(!hasSelection);

    }

}