blob: 5006884f5aff3fdafeb0bbdf9281cc2a5c599d6a [file] [log] [blame]
// GeographicControl.java
package org.eclipse.stem.ui.views.geographic;
/*******************************************************************************
* Copyright (c) 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Preferences.IPropertyChangeListener;
import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
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.AdapterFactory;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.URI;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.stem.core.common.Identifiable;
import org.eclipse.stem.core.graph.DynamicNodeLabel;
import org.eclipse.stem.core.graph.Edge;
import org.eclipse.stem.core.graph.Graph;
import org.eclipse.stem.core.graph.Node;
import org.eclipse.stem.core.model.Decorator;
import org.eclipse.stem.data.geography.centers.GeographicCenters;
import org.eclipse.stem.definitions.adapters.relativevalue.RelativeValueProvider;
import org.eclipse.stem.definitions.adapters.relativevalue.RelativeValueProviderAdapter;
import org.eclipse.stem.definitions.adapters.relativevalue.RelativeValueProviderAdapterFactory;
import org.eclipse.stem.definitions.adapters.spatial.geo.LatLong;
import org.eclipse.stem.definitions.adapters.spatial.geo.LatLong.Segment;
import org.eclipse.stem.definitions.adapters.spatial.geo.LatLongProvider;
import org.eclipse.stem.definitions.adapters.spatial.geo.LatLongProviderAdapter;
import org.eclipse.stem.definitions.adapters.spatial.geo.LatLongProviderAdapterFactory;
import org.eclipse.stem.definitions.edges.PopulationEdge;
import org.eclipse.stem.diseasemodels.standard.DiseaseModelLabel;
import org.eclipse.stem.jobs.simulation.ISimulation;
import org.eclipse.stem.jobs.simulation.ISimulationListener;
import org.eclipse.stem.jobs.simulation.SimulationEvent;
import org.eclipse.stem.populationmodels.standard.PopulationModelLabel;
import org.eclipse.stem.ui.Activator;
import org.eclipse.stem.ui.adapters.color.ColorProviderAdapter;
import org.eclipse.stem.ui.adapters.color.ColorProviderAdapterFactory;
import org.eclipse.stem.ui.adapters.color.IColorProviderChangedListener;
import org.eclipse.stem.ui.views.geographic.map.MapRenderer;
import org.eclipse.stem.ui.views.geographic.map.Messages;
import org.eclipse.stem.ui.views.geographic.map.StemPolygon;
import org.eclipse.stem.ui.views.geographic.map.StemPolygonsList;
import org.eclipse.stem.ui.widgets.GeoViewOptionsBar;
import org.eclipse.stem.ui.widgets.GeoViewOptionsBar.PropertySelectionEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.forms.widgets.FormToolkit;
/**
* This class represents a visualization of the geographic attributes of a
* {@link org.eclipse.stem.jobs.simulation.org.eclipse.stem.jobs.simulation.Simulation}.
*/
abstract public class GeographicControl extends Composite implements
ISimulationListener, ISelectionProvider, IColorProviderChangedListener,
IPropertyChangeListener, ISelectionChangedListener,
GeoViewOptionsBar.PropertySelectionListener {
/**
* contains expandable composites and other tools
*/
private FormToolkit toolkit;
/**
* remember the expansion state
*/
public boolean expansionState = true;
/**
* The width of the layout margin.
*/
protected static final int MARGIN_WIDTH = 5;
/**
* The height of the layout margin.
*/
protected static final int MARGIN_HEIGHT = 5;
/**
* This is the {@link org.eclipse.stem.jobs.simulation.Simulation} that this control is listening to and
* visualizing by rendering Lat/Long data on the {@link #mapCanvas}.
*/
private ISimulation simulation = null;
/**
* The {@link Identifiable} that was most recently selected, or
* <code>null</code>, if none has been selected.
*/
ISelection selection = null;
/**
* The collection of {@link ISelectionChangedListener} waiting to be told
* about selections.
*/
protected final List<ISelectionChangedListener> listeners = new CopyOnWriteArrayList<ISelectionChangedListener>();
protected GeographicRenderer geographicRenderer;
protected String selectedEdge = "";
/**
* If <code>true</code> then there is a map refresh request pending.
*/
boolean refreshPending = false;
/**
* This is the <code>Job</code> that computes the polygon values in the
* background.
*/
Job refreshJob;
/**
* Adapter instance of the selected color provider (to be reused by only
* setting the target)
*/
protected ColorProviderAdapter colorProviderAdapter = null;
private Label simulationNameLabel;
private GeoViewOptionsBar optionsBar;
/**
* This contains a mapping between the {@link Decorator}s we display and the
* data we need to display them. Not used yet.
*/
protected final Map<Decorator, DecoratorDisplayData> decoratorToDecoratorDisplayDataMap = new HashMap<Decorator, DecoratorDisplayData>();
// Cache of generated StemPolygonsList instances
private final Map<LatLong, StemPolygonsList> polygonListMap = new HashMap<LatLong, StemPolygonsList>();
/**
* @param parent
* @param style
*/
public GeographicControl(final Composite parent, final int style) {
super(parent, style);
Activator.getDefault().getPluginPreferences()
.addPropertyChangeListener(this);
setPreferences();
createContextMenu(this);
final FormLayout layout = new FormLayout();
layout.marginHeight = MARGIN_HEIGHT;
layout.marginWidth = MARGIN_WIDTH;
setLayout(layout);
geographicRenderer = createGeographicRenderer();
geographicRenderer.setColorProviderAdapter(colorProviderAdapter);
geographicRenderer.addSelectionChangedListener(this);
final Composite topComposite = createTopComposite(this);
final Composite bottomComposite = createBottomComposite(this);
final FormData topCompositeFormData = new FormData();
topComposite.setLayoutData(topCompositeFormData);
topCompositeFormData.top = new FormAttachment(0, 0);
topCompositeFormData.left = new FormAttachment(0, 0);
topCompositeFormData.right = new FormAttachment(100, 0);
final FormData geoRendererFormData = new FormData();
geographicRenderer.setLayoutData(geoRendererFormData);
geoRendererFormData.top = new FormAttachment(topComposite, 0);
geoRendererFormData.bottom = new FormAttachment(bottomComposite, 0);
geoRendererFormData.left = new FormAttachment(0, 0);
geoRendererFormData.right = new FormAttachment(100, 0);
// Bottom Composite
final FormData bottomCompositeFormData = new FormData();
bottomComposite.setLayoutData(bottomCompositeFormData);
bottomCompositeFormData.bottom = new FormAttachment(100, 0);
bottomCompositeFormData.left = new FormAttachment(0, 0);
bottomCompositeFormData.right = new FormAttachment(100, 0);
pack();
} // GeographicControl
/**
* @return
*/
abstract protected GeographicRenderer createGeographicRenderer();
/**
* Set visualization preferences
*/
protected void setPreferences() {
// Nothing yet
// try {
// // IPreferenceStore
// final Preferences preferences = Activator.getDefault()
// .getPluginPreferences();
//
// initialScaleRulerSelection = preferences
// .getInt(org.eclipse.stem.ui.views.geographic.map.preferences.
// PreferenceConstants.INITIAL_SCALE_SELECTION_PREFERENCE);
// } catch (final NullPointerException e) {
// // Ignore
// } // catch NullPointerException
} // setPreferences
/**
* @return the {@link org.eclipse.stem.jobs.simulation.org.eclipse.stem.jobs.simulation.Simulation}
*/
public final ISimulation getSimulation() {
return simulation;
} // getSimulation
/**
* Setting the {@link org.eclipse.stem.jobs.simulation.Simulation} has
* the side-effect of causing the control to remove itself as a listener
* from any previously set
* {@link org.eclipse.stem.jobs.simulation.Simulation} and adding itself
* as a listener to the new one. It will also cause the image to be
* initialized from the contents of the new
* {@link org.eclipse.stem.jobs.simulation.Simulation} as appropriate.
*
* @param simulation
* the {@link org.eclipse.stem.jobs.simulation.Simulation}
* whose Lat/Long data will be rendered.
*/
public final void setSimulation(final ISimulation simulation) {
// Any need to switch?
if (this.simulation == simulation) {
// No
return;
} // if no need to switch?
// Should we remove ourselves as listeners from the current simulation?
if (this.simulation != null) {
// Yes
this.simulation.removeSimulationListener(this);
}
this.simulation = simulation;
selection = null;
// Was there a new simulation to switch to?
if (this.simulation != null) {
// Yes
this.simulation.addSimulationListener(this);
initializeFromSimulation(this.simulation);
}
refresh();
} // setSimulation
/**
* Initialize the control from a
* {@link org.eclipse.stem.jobs.simulation.Simulation} instance.
*
* @param simulation
* the {@link org.eclipse.stem.jobs.simulation.Simulation}
* whose state will be visualized in the control.
*/
protected void initializeFromSimulation(final ISimulation simulation) {
simulationNameLabel.setText(simulation.getName());
optionsBar.setSimulation(simulation);
} // initializeFromSimulation
/**
* Update the contents of {@link #geographicRenderer} from the contents of
* the {@link #simulation}. This method should only be called by the UI
* thread otherwise it contains a race condition that can lock up the
* refreshing of the map canvas.
*/
void refresh() {
// This is complex and potential confusing code. The basic idea is that
// we want to determine what to draw on the mapCanvas in a thread
// separate from the UI thread, but we also want to capture a request to
// refresh the mapCanvas that comes while a background Job is already
// running. This request could have been generated because something
// significant like a decorator or a property has changed and
// we don't want to lose that switch.
// We have two fields that capture the state of the refresh. The first
// is the flag refreshPending which if true indicates that a call to
// refreshMapCanvas was processed while a background Job was executing.
// This flag is set instead of creating another background Job. The
// second is the field refreshJob which is set to the background Job if
// it is present and running.
//
// If we enter the method and refreshPending is true then we immediately
// exit because a refresh job will be created and there's nothing more
// for us to request. If, instead, refreshPending is false, then we
// check to see if a refreshJob is already running. If so, we set
// refreshPending to true, and exit. If not, we create the background
// refresh job and assign it to the field refreshJob.
//
// In the background refreshing job we process the Lat/Long data and
// then when done we add a Runnable on to the UI thread to redraw the
// mapCanvas. That Runnable also checks to see if the refresh pending
// flag is set and if so, resets the flag and then adds another Runnable
// to the UI thread to re-enter the refreshMapCanvas method.
//
// There is a race condition in the method if called by other than the
// UI thread. If a thread different from the UI thread were to call this
// method then it could reach position "RC #2" in the code below and
// then be preempted. The UI thread could then execute the test at "RC
// #1" below and find that there is no refresh pending. It would exit
// and then the other thread would resume at "RC #2" and set the
// refreshPending flag. This would cause all subsequent method
// invocations to immediately exit and the map canvas would never be
// refreshed again.
// Is there a refresh pending?
if (!refreshPending) {
// No
// Is there already a refresh job?
if (refreshJob == null) {
// No
// Refresh the map canvas in the background
refreshJob = new Job(Messages.getString("IMView.MRefresh")) {
/**
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected IStatus run(final IProgressMonitor monitor) {
final StemPolygonsList polygonsToDraw = createPolygonsToDraw(monitor);
// Still ok to run?
if(polygonsToDraw == null) return Status.OK_STATUS;
final Display display = Display.getDefault();
if (!display.isDisposed()) {
// Yes
try {
display.asyncExec(new Runnable() {
public void run() {
// This runs in the UI thread
// Is the renderer still around?
if (!geographicRenderer.isDisposed()) {
// Yes
geographicRenderer
.render(polygonsToDraw, simulation);
refreshJob = null;
// RC #1
// Is there a refresh pending?
if (refreshPending) {
// Yes
refreshPending = false;
// We create a new Runnable to
// avoid recursion
// Ok to run?
final Display display2 = Display
.getDefault();
if (!display2.isDisposed()) {
// Yes
display2
.asyncExec(new Runnable() {
public void run() {
// This runs
// in the UI
// thread
refresh();
} // asyncExec
});
} // if ok to run
} // if refresh pending
} // if the mapCanvas still around?
} // run (UI thread mapCanvas.redraw())
});
} // try
catch (final NullPointerException npe) {
// see 177966
// We ignore the exception, there's nothing to
// do
} // catch NullPointerException
} // if ok to run
monitor.done();
return Status.OK_STATUS;
} // run (background polygon processing Job)
};
refreshPending = false;
refreshJob.schedule();
} // if not already a refresh job
else {
// Yes
// There is already a refresh job so remember that a refresh is
// pending
// RC #2
refreshPending = true;
} // else
} // if no refresh is pending
} // refresh
/**
* @see org.eclipse.swt.widgets.Widget#dispose()
*/
@Override
public void dispose() {
super.dispose();
// Is there a simulation we're listening to?
if (simulation != null) {
// Yes
// Not any more
simulation.removeSimulationListener(this);
}
optionsBar.removeColorProviderChangedListener(this);
optionsBar.removePropertySelectionListener(this);
geographicRenderer.removeSelectionChangedListener(this);
// Shouldn't be any selection changed listeners
assert listeners.size() == 0;
Activator.getDefault().getPluginPreferences()
.removePropertyChangeListener(this);
} // dispose
/**
* Create the view's context menu and add the action handlers to it.
*/
private void createContextMenu(final Composite parent) {
// Context Menu
final MenuManager contextMenuManager = new MenuManager();
// ---------------------------------------------------------------------
contextMenuManager.add(new ResetRenderer());
// Place Holder for Menu Additions
contextMenuManager.add(new Separator(
IWorkbenchActionConstants.MB_ADDITIONS));
// ---------------------------------------------------------------------
final Menu popUpMenu = contextMenuManager.createContextMenu(parent);
// Set the context menu for the viewer
parent.setMenu(popUpMenu);
} // createContextMenu
/**
* @param event
*/
public void propertyChange(final PropertyChangeEvent event) {
setPreferences();
} // propertyChange
/**
* @param event
*/
public void selectionChanged(final SelectionChangedEvent event) {
fireSelection(event.getSelection());
}
protected class ResetRenderer extends Action {
/**
* @see org.eclipse.jface.action.Action#getText()
*/
@Override
public String getText() {
return Messages.getString("MapMenu.Reset");
}
/**
* @see org.eclipse.jface.action.Action#run()
*/
@Override
public void run() {
geographicRenderer.reset();
// setTranslation(DEFAULT_INITIAL_X_TRANSLATION,
// DEFAULT_INITIAL_Y_TRANSLATION);
// zoomFactor = INITIAL_ZOOM_FACTOR;
//
// // Reset the gain factor and update the checks in the context
// menu
// lastGainFactorAction.setChecked(false);
// defaultGainFactorAction.run();
// defaultGainFactorAction.setChecked(true);
//
// useLogScaling = DEFAULT_USE_LOGSCALING;
// logarithmicAction.setChecked(useLogScaling);
//
// drawPolygonBorders = DEFAULT_DRAW_POLYGON_BORDERS;
// drawPolygonBordersAction.setChecked(drawPolygonBorders);
//
// drawPolygonCenterConnections =
// DEFAULT_DRAW_POLYGON_CENTER_CONNECTIONS;
// connectPolygonCentersAction
// .setChecked(drawPolygonCenterConnections);
geographicRenderer.redraw();
}
} // ResetMapCanvasAction
/**
* This class is used to hold data used to display the relative values of a
* {@link Decorator}. Instances of this class are created when the label
* values of a {@link Decorator} are first drawn and then subsequently
* cached in {@link #decoratorToDecoratorDisplayDataMap} for future
* retrieval.
*/
protected static class DecoratorDisplayData {
private RelativeValueProviderAdapter rvp;
private LatLongProviderAdapter latLongProvider;
private final List<LabelData> labelDatas = new ArrayList<LabelData>();
private final List<LabelData> labelsWithUnresolvedLatLong = new ArrayList<LabelData>();
/**
* Default constructor
*/
public DecoratorDisplayData() {
// nothing
}
/**
* @return <code>true</code> if all of the labels have valid
* latitude/longitude data retrieved.
*/
public boolean hasAllLatLong() {
return labelsWithUnresolvedLatLong.size() == 0;
}
/**
* @return the label data for the decorator
*/
public List<LabelData> getLabelData() {
return labelDatas;
}
/**
* @return the {@link RelativeValueProviderAdapter}
*/
public RelativeValueProviderAdapter getRelativeValueProviderAdapter() {
return rvp;
} // getRelativeValueProviderAdapter
/**
* Go through each of the label data entries and try to retrieve the
* ones that do not have lat/long data yet.
*
* @param monitor
* progress monitor
*/
void updateLatLong(final IProgressMonitor monitor) {
for (final Iterator<LabelData> iter = labelsWithUnresolvedLatLong
.iterator(); iter.hasNext();) {
final LabelData ld = iter.next();
latLongProvider.setTarget(ld.getLabel().getNode());
ld.setLatLongData(latLongProvider.getLatLongNoWait());
// Did we get some data?
LatLong data = ld.getLatLongData();
if (data != null && data.size() != 0) {
// Yes
// No need to keep this one on the list
iter.remove();
} // if we got some data
} // for each DynamicNodeLabelImpl
} // updateLatLong
/**
* @param label
* a {@link DynamicNodeLabel} to add to the set.
*/
public void addLabel(final DynamicNodeLabel label) {
final LabelData ld = new LabelData(label);
labelDatas.add(ld);
labelsWithUnresolvedLatLong.add(ld);
} // addLabel
/**
* @param latLongProvider
* the {@link LatLongProviderAdapter} that will provide the
* latitude/longitude data for the labels.
*/
public void setLatLongProviderAdapter(
final LatLongProviderAdapter latLongProvider) {
this.latLongProvider = latLongProvider;
}
/**
* @param rvp
* the {@link RelativeValueProviderAdapter} that will provide
* the relative values for the labels.
*/
public void setRelativeValueProviderAdapter(
final RelativeValueProviderAdapter rvp) {
this.rvp = rvp;
} // setRelativeValueProviderAdapter
/**
* This class maintains a relationship between a
* {@link org.eclipse.stem.core.graph.Label} and its
* latitude/longitude data.
*/
public static class LabelData {
private final DynamicNodeLabel label;
private LatLong latLongData;
/**
* @param label
*/
public LabelData(final DynamicNodeLabel label) {
this.label = label;
}
/**
* @return the label
*/
public DynamicNodeLabel getLabel() {
return label;
}
/**
* @return the latitude/longitude data
*/
public LatLong getLatLongData() {
return latLongData;
}
/**
* @param latLongData
*/
public void setLatLongData(final LatLong latLongData) {
this.latLongData = latLongData;
}
} // LabelData
}
/**
* @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
*/
public void addSelectionChangedListener(
final ISelectionChangedListener listener) {
listeners.add(listener);
}
/**
* @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
*/
public void removeSelectionChangedListener(
final ISelectionChangedListener listener) {
listeners.remove(listener);
}
/**
* @return the selection
*/
public final ISelection getSelection() {
return selection;
}
/**
* @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection)
*/
public void setSelection(final ISelection selection) {
this.selection = selection;
fireSelection(selection);
}
/**
* Pass a {@link SelectionChangedEvent} along to listeners.
*
* @param selection
* the {@link ISelection} to be giving to the listeners.
*/
public void fireSelection(final ISelection selection) {
final SelectionChangedEvent event = new SelectionChangedEvent(this,
selection);
for (final ISelectionChangedListener listener : listeners) {
listener.selectionChanged(event);
} // for each ISelectionChangedListener
} // fireSelection
/**
* @param parent
* @return
*/
protected Composite createBottomComposite(final Composite parent) {
// toolkit = new FormToolkit(parent.getDisplay());
// ExpandableComposite ec = toolkit.createExpandableComposite(parent, ExpandableComposite.TREE_NODE);
// ec.setText("Map Controls");
// ec.setExpanded(expansionState);
// ec.addExpansionListener(new ExpansionAdapter() {
// public void expansionStateChanged(ExpansionEvent e) {
// //Activator.logInformation("toggle me now");
// }
// });
// optionsBar = new GeoViewOptionsBar(ec, SWT.NONE);
optionsBar = new GeoViewOptionsBar(parent, SWT.NONE);
optionsBar.addColorProviderChangedListener(this);
optionsBar.addPropertySelectionListener(this);
// ec.setClient(optionsBar);
// return ec;
return optionsBar;
} // createBottomComposite
/**
* @param propertySelectionEvent
*/
public void propertySelected(
final PropertySelectionEvent propertySelectionEvent) {
if (propertySelectionEvent == null) {
selectedEdge = "";
}
// Hack
else if (propertySelectionEvent.getSource() instanceof String) {
selectedEdge = (String) propertySelectionEvent.getSource();
}
refresh();
}
/**
* @param parent
* the parent SWT control that this {@link Composite} will be a
* child of
* @return the SWT {@link Composite} to be displayed above the
* {@link org.eclipse.stem.ui.views.geographic.map.MapCanvas} in the
* control
*/
protected Composite createTopComposite(final Composite parent) {
final Composite topComposite = new Composite(parent, SWT.NONE);
final FillLayout topCompositeLayout = new FillLayout(SWT.HORIZONTAL);
topComposite.setLayout(topCompositeLayout);
simulationNameLabel = new Label(topComposite, SWT.LEFT);
simulationNameLabel.setText("");
// JHK: Adding gain and log/linear scale info to the top composite
geographicRenderer.gainScaleLabel = new Label(topComposite, SWT.RIGHT);
geographicRenderer.updateScaleLogLinLabel();
return topComposite;
} // createTopComposite
/**
* @see org.eclipse.stem.jobs.simulation.ISimulationListener#simulationChanged(org.eclipse.stem.jobs.simulation.SimulationEvent)
*/
public void simulationChanged(final SimulationEvent event) {
switch (event.getSimulationState()) {
case COMPLETED_CYCLE:
// It could be that the propertySelector is not initialized
// yet. This could happen if, when the simulation is first set, its
// scenario was not completely initialized, and so didn't have a
// canonical graph. If this is the case, we need to try again.
// Is the control initialized?
if (!optionsBar.isInitialized()) {
// No
optionsBar.setSimulation(getSimulation());
} // if
refresh();
// un-comment this to save a bit map image file of the map
//
// saveCanvasImage(event.getSimulation());
//
//////////////////////////////////////////
break;
case RESET:
refresh();
break;
default:
// Ignore
break;
} // switch
} // simulationChanged
/**
* This runs in the background {@link Job} created in {@link #refresh()}
*
* @param decorator
* the {@link Decorator} for whom that data is being retrieved.
* @param monitor
* the progress monitor to use to report the progress of
* obtaining the data
* @return the {@link DecoratorDisplayData} for the {@link Decorator}.
*/
protected DecoratorDisplayData getDecoratorDisplayData(
final Decorator decorator, final IProgressMonitor monitor) {
DecoratorDisplayData retValue = decoratorToDecoratorDisplayDataMap
.get(decorator);
// Is there already an instance for this decorator?
if (retValue == null) {
// No
// Create one then
retValue = new DecoratorDisplayData();
decoratorToDecoratorDisplayDataMap.put(decorator, retValue);
if (decorator.getLabelsToUpdate() == null
|| decorator.getLabelsToUpdate().isEmpty()) {
return retValue;
}
// Get the relative value provider adapter that we'll use for all of
// the decorator's labels. We only get it once because it is
// implemented using the singleton pattern so we'll always get the
// same one.
final DynamicNodeLabel firstNodeLabel = (DynamicNodeLabel) decorator
.getLabelsToUpdate().get(0);
// Was there a label to update?
if (firstNodeLabel != null) {
// Yes
// Get the relative value from the dynamic label
final RelativeValueProviderAdapter rvp = (RelativeValueProviderAdapter) RelativeValueProviderAdapterFactory.INSTANCE
.adapt(firstNodeLabel, RelativeValueProvider.class);
if(rvp == null) return null;
retValue.setRelativeValueProviderAdapter(rvp);
final Node node = firstNodeLabel.getNode();
// String nodeTitle = node.getDublinCore().getTitle();
final LatLongProviderAdapter latLongProvider = (LatLongProviderAdapter) LatLongProviderAdapterFactory.INSTANCE
.adapt(node, LatLongProvider.class);
retValue.setLatLongProviderAdapter(latLongProvider);
} // if there is a label
for (final Object element : decorator.getLabelsToUpdate()) {
final DynamicNodeLabel nodeLabel = (DynamicNodeLabel) element;
retValue.addLabel(nodeLabel);
} // for
} // if no instance
// Does the instance have all of it's lat/long data retrieved yet?
if (!retValue.hasAllLatLong()) {
// No
// Try to get it
monitor.subTask(decorator.getDublinCore().getTitle());
retValue.updateLatLong(monitor);
} // if missing lat/long
return retValue;
} // getDecoratorDisplayData
protected StemPolygonsList createPolygonsToDraw(
final IProgressMonitor monitor) {
return createPolygonsToDraw(optionsBar.getSelectedDecorator(), optionsBar.getSelectedPopulationIdentifier(), monitor);
} // createPolygonsToDraw
/**
* This runs in the background {@link Job} created in
* {@link #refreshMapCanvas()}.
*
* @param selectedDecorator
* the {@link Decorator} that contains the selected property.
* @param seletedPopulationIdentifier The selected population identifier
* @param selectedProperty
* the property of the selected {@link Decorator} to visualize
* @param monitor
* progress monitor for obtaining the polygons
* @return a collection of polygon values to render in the
* {@link #mapCanvas}
*/
StemPolygonsList createPolygonsToDraw(final Decorator selectedDecorator, String selectedPopulationIdentifier,
final IProgressMonitor monitor) {
final StemPolygonsList retValue = new StemPolygonsList();
// Is there a decorator selected?
if (selectedDecorator != null) {
// Yes
// Get the display data for this decorator
final DecoratorDisplayData displayData = getDecoratorDisplayData(
selectedDecorator, monitor);
if(displayData == null) return null;
final RelativeValueProviderAdapter rvp = displayData
.getRelativeValueProviderAdapter();
if(rvp == null) return null;
monitor.beginTask(selectedDecorator.getDublinCore().getTitle(),
displayData.getLabelData().size());
for (final DecoratorDisplayData.LabelData labelData : displayData
.getLabelData()) {
// Check that the population identifier matches. If not, skip it
DynamicNodeLabel lab = labelData.getLabel();
if(lab instanceof DiseaseModelLabel &&
!((DiseaseModelLabel)lab).getPopulationModelLabel().getPopulationIdentifier().equals(selectedPopulationIdentifier)) continue;
if(lab instanceof PopulationModelLabel &&
!((PopulationModelLabel)lab).getPopulationIdentifier().equals(selectedPopulationIdentifier)) continue;
// This is the Identifiable that will produce the lat/long data
// (if available).
final Identifiable identifiable = labelData.getLabel()
.getIdentifiable();
monitor.subTask(identifiable.getDublinCore().getTitle());
final LatLong latLong = labelData.getLatLongData();
// Any lat/long data for this label?
if (latLong != null && latLong.size() > 0) {
// Yes
final StemPolygonsList stemPolygonsList = getStemPolygonsList(
latLong, identifiable, rvp, labelData);
// adding it to the list of polygons to be drawn
retValue.addAll(stemPolygonsList);
} // if Any lat/long data for this label
monitor.worked(1);
} // for each LabelData
if (selectedEdge != null && !selectedEdge.equals("")) {
final List<URI> edgeTypes = new LinkedList<URI>();
final Graph graph = selectedDecorator.getGraph();
final EMap<URI, Edge> allEdges = graph.getEdges();
final Iterator<URI> iter = allEdges.keySet().iterator();
final List<Edge> edges = new ArrayList<Edge>();
while (iter.hasNext()) {
final URI uri = iter.next();
if (edgeTypes.contains(uri)) {
edgeTypes.add(uri);
}
if (uri.toString().startsWith(selectedEdge)) {
Edge edge = allEdges.get(uri);
if (edge instanceof PopulationEdge) {
String popID = ((PopulationEdge) edge).getPopulationIdentifier();
if (selectedPopulationIdentifier.equals(popID)) edges.add(edge);
}
else {
edges.add(edge);
}
}
}
if (!edges.isEmpty()) {
final Iterator<Edge> edgesIter = edges.iterator();
final List<URI> addedEdges = new ArrayList<URI>();
while (edgesIter.hasNext()) {
final Edge nextEdge = edgesIter.next();
final Node nodeA = nextEdge.getA();
final Node nodeB = nextEdge.getB();
if (nodeA == null || nodeB == null) {
// Commented out 4/2/2009 since it is possible
// that edges for air transport has a missing node if
// a sub model is not included in the scenario
/*final StringBuffer missingEdgeReport = new StringBuffer();
missingEdgeReport
.append("The following edge has a missing node:\n");
missingEdgeReport.append(" Edge URI: "
+ nextEdge.getURI() + "\n");
if (nodeA == null && nodeB == null) {
missingEdgeReport.append(" Missing Node: Both");
} else {
missingEdgeReport.append(" Missing Node: "
+ (nodeA == null ? "A" : "B"));
missingEdgeReport.append(" Non-missing Node: "
+ (nodeA == null ? "B" : "A"));
}
Activator.logError(missingEdgeReport.toString(),
null);
*/
continue;
}
final String nodeAISOKey = nodeA.getURI().lastSegment();
double[] centerA = GeographicCenters
.getCenter(nodeAISOKey);
// Did we get it?
if (centerA == null) {
// No
// Compute the lat/long of the center of the node
final LatLongProviderAdapter latLongProviderA = (LatLongProviderAdapter) LatLongProviderAdapterFactory.INSTANCE
.adapt(nodeA, LatLongProvider.class);
latLongProviderA.setTarget(nodeA);
centerA = latLongProviderA.getCenter();
} // if
final String nodeBISOKey = nodeB.getURI().lastSegment();
double[] centerB = GeographicCenters
.getCenter(nodeBISOKey);
// Did we get it?
if (centerB == null) {
// No
// Get the lat/long of the center of the node
final LatLongProviderAdapter latLongProviderB = (LatLongProviderAdapter) LatLongProviderAdapterFactory.INSTANCE
.adapt(nodeB, LatLongProvider.class);
latLongProviderB.setTarget(nodeB);
centerB = latLongProviderB.getCenter();
} // if centerB
if (centerA == null || centerB == null) {
continue;
}
final double[][] data = new double[2][2];
data[0][0] = centerA[0];
data[0][1] = centerA[1];
data[1][0] = centerB[0];
data[1][1] = centerB[1];
if (!addedEdges.contains(nextEdge.getURI())) {
final Segment edgeSegment = new Segment(data);
final StemPolygon stemPolygon = new StemPolygon(
edgeSegment, nextEdge);
retValue.add(stemPolygon);
addedEdges.add(nextEdge.getURI());
}
}
}
}
} // if there a decorator and a currently selected property
return retValue;
}// createPolygonsToDraw
/**
* @param latLong
* @param rvp
* @return
*/
private StemPolygonsList getStemPolygonsList(final LatLong latLong,
final Identifiable identifiable,
final RelativeValueProviderAdapter rvp,
final DecoratorDisplayData.LabelData labelData) {
StemPolygonsList retValue = polygonListMap.get(latLong);
rvp.setTarget(labelData.getLabel());
// final ItemPropertyDescriptor selectedProperty =
// null;//bottomComposite.get
// Did we find a previously generated polygon list?
if (retValue == null) {
// // No
// // Allocate a mapping between the property names and their
// // relative values.
// final Map<String, Double> relativeValueMap = new HashMap<String,
// Double>();
//
// // First put the Selected property in the valueMap to
// // display
// relativeValueMap.put(selectedProperty
// .getDisplayName(selectedProperty), new Double(rvp
// .getRelativeValue(selectedProperty)));
// Are we in multicolor display mode?
// if (false /* mapCanvas.enableMultiColorMode */) {
// // Yes
// // Add all relative values by property attribute key
// final List<IItemPropertyDescriptor> properties = rvp
// .getProperties();
// // JHK we don't need to add the selected one separately
// // above as the code below should get it as well - check
// // this...
// assert (properties.contains(selectedProperty));
// // Add ALL the properties for MultiColor Display Mode
// for (int i = 0; i < properties.size(); i++) {
// final ItemPropertyDescriptor itemKey = (ItemPropertyDescriptor)
// properties
// .get(i);
// key = itemKey.getDisplayName(itemKey);
// relativeValue = new Double(rvp.getRelativeValue(itemKey));
// valueMap.put(key, relativeValue);
// } // for all properties
// } // if multi color mode
// creating the polygon from that label
retValue = new StemPolygonsList(latLong, // relativeValueMap,
identifiable);
polygonListMap.put(latLong, retValue);
} // if didn't find generated instance
// else {
// // Update the relative values of the properties in the value map
// retValue.clearRelativeValues();
// retValue.addRelativeValue(selectedProperty
// .getDisplayName(selectedProperty), new Double(rvp
// .getRelativeValue(selectedProperty)));
// } // else
return retValue;
} // getStemPolygonsList
/**
* The method will return the {@link GeographicRenderer} used by this
* object.
*
* @return The GeographicRenderer used by this object
* @see GeographicRenderer
*/
public GeographicRenderer getGeographicRenderer() {
return geographicRenderer;
} // getGeographicRenderer
/**
* @see org.eclipse.stem.ui.adapters.color.IColorProviderChangedListener#colorProviderChanged(java.lang.Class)
*/
public void colorProviderChanged(final Class selectedColorProvider) {
final Decorator decorator = optionsBar.getSelectedDecorator();
if(decorator == null) return;
final AdapterFactory adapterFactory = ColorProviderAdapterFactory.INSTANCE
.getFactoryForType(selectedColorProvider);
final Adapter adapter = adapterFactory.adapt(decorator.getGraph(),
selectedColorProvider);
if (adapter instanceof ColorProviderAdapter) {
colorProviderAdapter = (ColorProviderAdapter) adapter;
colorProviderAdapter.setSelectedDecorator(optionsBar
.getSelectedDecorator());
colorProviderAdapter.setSelectedPopulationIdentifier(optionsBar.getSelectedPopulationIdentifier());
geographicRenderer.setColorProviderAdapter(colorProviderAdapter);
layout();
}
} // colorProviderChanged
private static final IPath WORKSPACE_PATH = ResourcesPlugin.getWorkspace().getRoot().getLocation();
private static final String sep = File.separator;
private static final String scenariosURLPrefix = "platform:/resource/";
private static final String builtInScenarioProjectFolder = "BuiltInScenarios";
private static final String recordedSimulationsFolderName = "RecordedMapImages";
private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());
private static File getImageFileToLog(ISimulation sim) {
String scenarioURL = sim.getScenario().getURI().toString();
boolean builtInScenario = scenarioURL.startsWith(scenariosURLPrefix);
IPath path1;
if(builtInScenario) {
String scenarioRelativePath = scenarioURL.substring(scenariosURLPrefix.length());
path1 = WORKSPACE_PATH.append(scenarioRelativePath).removeLastSegments(2);
} else {
path1 = WORKSPACE_PATH.append(builtInScenarioProjectFolder);
}
String path = path1
.append(recordedSimulationsFolderName)
.append(sim.getUniqueIDString())
.append(DATE_FORMATTER.format(sim.getScenario().getSequencer().getCurrentTime().getTime()) +".png")
.toOSString();
File f = new File(path);
f.getParentFile().mkdirs();
return f;
}
public Image getMapCanvasImage()
{
if (geographicRenderer instanceof MapRenderer) {
return ((MapRenderer)geographicRenderer).getCanvas().getCanvasImage();
}
return null;
}
/**
* Saves the current map canvas image to a file based on the simulation/scenario
* name and current simulation date.
*
* @param sim Simulation/Scenario to save map canvas for
*/
private void saveCanvasImage(ISimulation sim)
{
if (geographicRenderer instanceof MapRenderer) {
try {
((MapRenderer)geographicRenderer).getCanvas().saveCanvasImage(getImageFileToLog(sim));
} catch (IOException ioe) {
Activator.logInformation("Error saving map canvas image", ioe);
}
}
}
} // GeographicControl