| /******************************************************************************* |
| * 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 |
| * Bundesinstitut für Risikobewertung |
| *******************************************************************************/ |
| |
| package org.eclipse.stem.ui.views.graphmap; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| 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.AdapterFactory; |
| import org.eclipse.emf.common.util.EMap; |
| import org.eclipse.emf.common.util.URI; |
| 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.DynamicLabel; |
| import org.eclipse.stem.core.graph.DynamicNodeLabel; |
| import org.eclipse.stem.core.graph.Edge; |
| 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.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.adapters.color.ColorProviderAdapter; |
| import org.eclipse.stem.ui.adapters.color.ColorProviderAdapterFactory; |
| import org.eclipse.stem.ui.adapters.color.IColorProviderChangedListener; |
| import org.eclipse.stem.ui.widgets.GeoViewOptionsBar; |
| import org.eclipse.stem.ui.widgets.GeoViewOptionsBar.PropertySelectionEvent; |
| import org.eclipse.swt.SWT; |
| 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; |
| |
| /** |
| * This class represents a visualization of the geographic attributes of a |
| * {@link org.eclipse.stem.jobs.simulation.org.eclipse.stem.jobs.simulation.Simulation} |
| * . |
| */ |
| public class GraphMapControl extends Composite implements ISimulationListener, |
| ISelectionProvider, IColorProviderChangedListener, |
| ISelectionChangedListener, GeoViewOptionsBar.PropertySelectionListener { |
| |
| private ISimulation simulation; |
| private ISelection selection; |
| private List<ISelectionChangedListener> listeners; |
| private GraphMapRenderer geographicRenderer; |
| private String selectedEdge; |
| private Job refreshJob; |
| private boolean refreshPending; |
| private StemPolygonList polygonsToDraw; |
| private ColorProviderAdapter colorProviderAdapter; |
| private Label simulationNameLabel; |
| private GeoViewOptionsBar optionsBar; |
| private Map<Decorator, DecoratorDisplayData> decoratorToDecoratorDisplayDataMap; |
| private Map<LatLong, StemPolygonList> polygonListMap; |
| |
| public GraphMapControl(Composite parent, int style) { |
| super(parent, style); |
| listeners = new CopyOnWriteArrayList<ISelectionChangedListener>(); |
| decoratorToDecoratorDisplayDataMap = new HashMap<Decorator, DecoratorDisplayData>(); |
| polygonListMap = new HashMap<LatLong, StemPolygonList>(); |
| geographicRenderer = new GraphMapRenderer(this, SWT.NONE); |
| geographicRenderer.setColorProviderAdapter(colorProviderAdapter); |
| geographicRenderer.addSelectionChangedListener(this); |
| |
| setLayout(new FormLayout()); |
| |
| Composite topComposite = new Composite(this, SWT.NONE); |
| topComposite.setLayout(new FillLayout(SWT.HORIZONTAL)); |
| simulationNameLabel = new Label(topComposite, SWT.LEFT); |
| simulationNameLabel.setText(""); |
| geographicRenderer.setGainScaleLabel(new Label(topComposite, SWT.RIGHT)); |
| geographicRenderer.updateScaleLogLinLabel(); |
| |
| optionsBar = new GeoViewOptionsBar(this, SWT.NONE); |
| optionsBar.addColorProviderChangedListener(this); |
| optionsBar.addPropertySelectionListener(this); |
| |
| 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); |
| |
| FormData geoRendererFormData = new FormData(); |
| geographicRenderer.setLayoutData(geoRendererFormData); |
| geoRendererFormData.top = new FormAttachment(topComposite, 0); |
| geoRendererFormData.bottom = new FormAttachment(optionsBar, 0); |
| geoRendererFormData.left = new FormAttachment(0, 0); |
| geoRendererFormData.right = new FormAttachment(100, 0); |
| |
| FormData optionsBarFormData = new FormData(); |
| optionsBar.setLayoutData(optionsBarFormData); |
| optionsBarFormData.bottom = new FormAttachment(100, 0); |
| optionsBarFormData.left = new FormAttachment(0, 0); |
| optionsBarFormData.right = new FormAttachment(100, 0); |
| |
| pack(); |
| } |
| |
| public ISimulation getSimulation() { |
| return simulation; |
| } |
| |
| /** |
| * 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 void setSimulation(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); |
| simulationNameLabel.setText(this.simulation.getName()); |
| optionsBar.setSimulation(this.simulation); |
| } |
| |
| refresh(); |
| } |
| |
| /** |
| * 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. |
| */ |
| public 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. |
| |
| if (refreshJob != null) { |
| refreshPending = true; |
| return; |
| } |
| |
| refreshJob = new Job(Messages.getString("IMView.MRefresh")) { |
| |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| polygonsToDraw = createPolygonsToDraw(monitor); |
| |
| if (polygonsToDraw == null) { |
| return Status.OK_STATUS; |
| } |
| |
| try { |
| Display display = Display.getDefault(); |
| |
| if (!display.isDisposed()) { |
| display.asyncExec(new Runnable() { |
| |
| @Override |
| public void run() { |
| if (!geographicRenderer.isDisposed()) { |
| geographicRenderer.render(polygonsToDraw, |
| simulation); |
| refreshJob = null; |
| |
| if (refreshPending) { |
| refreshPending = false; |
| refresh(); |
| } |
| } else { |
| refreshJob = null; |
| } |
| } |
| }); |
| } |
| } catch (NullPointerException npe) { |
| // See 177966: |
| // We ignore the exception, there's nothing to do |
| } |
| |
| monitor.done(); |
| |
| return Status.OK_STATUS; |
| } |
| }; |
| refreshJob.schedule(); |
| } |
| |
| /** |
| * The method will return the {@link GeographicRenderer} used by this |
| * object. |
| * |
| * @return The GeographicRenderer used by this object |
| * @see GeographicRenderer |
| */ |
| public GraphMapRenderer getGeographicRenderer() { |
| return geographicRenderer; |
| } |
| |
| /** |
| * Pass a {@link SelectionChangedEvent} along to listeners. |
| * |
| * @param selection |
| * the {@link ISelection} to be giving to the listeners. |
| */ |
| public void fireSelection(ISelection selection) { |
| SelectionChangedEvent event = new SelectionChangedEvent(this, selection); |
| |
| for (ISelectionChangedListener listener : listeners) { |
| listener.selectionChanged(event); |
| } |
| |
| } |
| |
| @Override |
| public void simulationChanged(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. |
| |
| if (!optionsBar.isInitialized()) { |
| optionsBar.setSimulation(getSimulation()); |
| } |
| |
| refresh(); |
| break; |
| case RESET: |
| refresh(); |
| break; |
| } |
| } |
| |
| @Override |
| public void colorProviderChanged(Class selectedColorProvider) { |
| Decorator decorator = optionsBar.getSelectedDecorator(); |
| |
| if (decorator == null) { |
| return; |
| } |
| |
| AdapterFactory adapterFactory = ColorProviderAdapterFactory.INSTANCE |
| .getFactoryForType(selectedColorProvider); |
| 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(); |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| super.dispose(); |
| optionsBar.removeColorProviderChangedListener(this); |
| optionsBar.removePropertySelectionListener(this); |
| geographicRenderer.removeSelectionChangedListener(this); |
| |
| if (simulation != null) { |
| simulation.removeSimulationListener(this); |
| } |
| |
| // Shouldn't be any selection changed listeners |
| assert listeners.size() == 0; |
| } |
| |
| @Override |
| public void selectionChanged(SelectionChangedEvent event) { |
| fireSelection(event.getSelection()); |
| } |
| |
| @Override |
| public void addSelectionChangedListener(ISelectionChangedListener listener) { |
| listeners.add(listener); |
| } |
| |
| @Override |
| public void removeSelectionChangedListener( |
| ISelectionChangedListener listener) { |
| listeners.remove(listener); |
| } |
| |
| @Override |
| public ISelection getSelection() { |
| return selection; |
| } |
| |
| @Override |
| public void setSelection(ISelection selection) { |
| this.selection = selection; |
| fireSelection(selection); |
| } |
| |
| @Override |
| public void propertySelected(PropertySelectionEvent propertySelectionEvent) { |
| if (propertySelectionEvent == null) { |
| selectedEdge = null; |
| } else if (propertySelectionEvent.getSource() instanceof String) { |
| selectedEdge = (String) propertySelectionEvent.getSource(); |
| } |
| |
| refresh(); |
| } |
| |
| /** |
| * 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}. |
| */ |
| private DecoratorDisplayData getDecoratorDisplayData(Decorator decorator, |
| IProgressMonitor monitor) { |
| DecoratorDisplayData retValue = decoratorToDecoratorDisplayDataMap |
| .get(decorator); |
| |
| // Is there already an instance for this decorator? |
| if (retValue == null) { |
| retValue = new DecoratorDisplayData(); |
| decoratorToDecoratorDisplayDataMap.put(decorator, retValue); |
| |
| if (decorator.getLabelsToUpdate() == null |
| || decorator.getLabelsToUpdate().isEmpty()) { |
| return retValue; |
| } |
| |
| DynamicNodeLabel firstNodeLabel = (DynamicNodeLabel) decorator |
| .getLabelsToUpdate().get(0); |
| |
| if (firstNodeLabel != null) { |
| Node node = firstNodeLabel.getNode(); |
| LatLongProviderAdapter latLongProvider = (LatLongProviderAdapter) LatLongProviderAdapterFactory.INSTANCE |
| .adapt(node, LatLongProvider.class); |
| |
| retValue.setLatLongProviderAdapter(latLongProvider); |
| } |
| |
| for (DynamicLabel label : decorator.getLabelsToUpdate()) { |
| retValue.addLabel((DynamicNodeLabel) label); |
| } |
| } |
| |
| // Does the instance have all of it's lat/long data retrieved yet? |
| if (!retValue.hasAllLatLong()) { |
| monitor.subTask(decorator.getDublinCore().getTitle()); |
| retValue.updateLatLong(monitor); |
| } |
| |
| return retValue; |
| } |
| |
| /** |
| * This runs in the background {@link Job} created in |
| * {@link #refreshMapCanvas()}. |
| * |
| * @param monitor |
| * progress monitor for obtaining the polygons |
| * @return a collection of polygon values to render in the |
| * {@link #mapCanvas} |
| */ |
| private StemPolygonList createPolygonsToDraw(IProgressMonitor monitor) { |
| Decorator selectedDecorator = optionsBar.getSelectedDecorator(); |
| String selectedPopulationIdentifier = optionsBar |
| .getSelectedPopulationIdentifier(); |
| StemPolygonList retValue = new StemPolygonList(); |
| |
| if (selectedDecorator == null) { |
| return null; |
| } |
| |
| DecoratorDisplayData displayData = getDecoratorDisplayData( |
| selectedDecorator, monitor); |
| |
| if (displayData == null) { |
| return null; |
| } |
| |
| monitor.beginTask(selectedDecorator.getDublinCore().getTitle(), |
| displayData.getLabelData().size()); |
| |
| for (Map.Entry<DynamicNodeLabel, LatLong> labelData : displayData |
| .getLabelData().entrySet()) { |
| DynamicNodeLabel lab = labelData.getKey(); |
| |
| // Check that the population identifier matches. If not, skip it |
| if (lab instanceof DiseaseModelLabel |
| && !((DiseaseModelLabel) lab).getPopulationModelLabel() |
| .getPopulationIdentifier() |
| .equals(selectedPopulationIdentifier)) { |
| continue; |
| } else if (lab instanceof PopulationModelLabel |
| && !((PopulationModelLabel) lab).getPopulationIdentifier() |
| .equals(selectedPopulationIdentifier)) { |
| continue; |
| } |
| |
| // This is the Identifiable that will produce the lat/long data. |
| Identifiable identifiable = labelData.getKey().getIdentifiable(); |
| LatLong latLong = labelData.getValue(); |
| |
| monitor.subTask(identifiable.getDublinCore().getTitle()); |
| |
| if (latLong != null && latLong.size() > 0) { |
| StemPolygonList stemPolygonsList = getStemPolygonsList(latLong, |
| identifiable); |
| |
| retValue.addAll(stemPolygonsList); |
| } |
| |
| monitor.worked(1); |
| } |
| |
| if (selectedEdge == null) { |
| return retValue; |
| } |
| |
| EMap<URI, Edge> allEdges = selectedDecorator.getGraph().getEdges(); |
| List<Edge> edges = new ArrayList<Edge>(); |
| |
| for (URI uri : allEdges.keySet()) { |
| 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()) { |
| return retValue; |
| } |
| |
| Set<URI> addedEdges = new HashSet<URI>(); |
| |
| for (Edge nextEdge : edges) { |
| Node nodeA = nextEdge.getA(); |
| Node nodeB = nextEdge.getB(); |
| |
| if (nodeA == null || nodeB == null) { |
| // It is possible that edges for air transport has a missing |
| // node if sub model is not included in the scenario. |
| continue; |
| } |
| |
| String nodeAISOKey = nodeA.getURI().lastSegment(); |
| String nodeBISOKey = nodeB.getURI().lastSegment(); |
| double[] centerA = GeographicCenters.getCenter(nodeAISOKey); |
| double[] centerB = GeographicCenters.getCenter(nodeBISOKey); |
| |
| if (centerA == null) { |
| LatLongProviderAdapter latLongProviderA = (LatLongProviderAdapter) LatLongProviderAdapterFactory.INSTANCE |
| .adapt(nodeA, LatLongProvider.class); |
| |
| latLongProviderA.setTarget(nodeA); |
| centerA = latLongProviderA.getCenter(); |
| } |
| |
| if (centerB == null) { |
| LatLongProviderAdapter latLongProviderB = (LatLongProviderAdapter) LatLongProviderAdapterFactory.INSTANCE |
| .adapt(nodeB, LatLongProvider.class); |
| |
| latLongProviderB.setTarget(nodeB); |
| centerB = latLongProviderB.getCenter(); |
| } |
| |
| if (centerA == null || centerB == null) { |
| continue; |
| } |
| |
| if (!addedEdges.contains(nextEdge.getURI())) { |
| Segment edgeSegment = new Segment(new double[][] { centerA, |
| centerB }); |
| StemPolygon stemPolygon = new StemPolygon(edgeSegment, nextEdge); |
| |
| retValue.add(stemPolygon); |
| addedEdges.add(nextEdge.getURI()); |
| } |
| } |
| |
| return retValue; |
| } |
| |
| private StemPolygonList getStemPolygonsList(LatLong latLong, |
| Identifiable identifiable) { |
| StemPolygonList retValue = polygonListMap.get(latLong); |
| |
| if (retValue == null) { |
| retValue = new StemPolygonList(latLong, identifiable); |
| polygonListMap.put(latLong, retValue); |
| } |
| |
| return retValue; |
| } |
| |
| } |