/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jump.workbench.model; import java.awt.Color; import java.awt.geom.Line2D; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.swing.JInternalFrame; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKTReader; import com.vividsolutions.jts.util.Assert; import com.vividsolutions.jump.coordsys.CoordinateSystem; import com.vividsolutions.jump.coordsys.Reprojector; import com.vividsolutions.jump.feature.Feature; import com.vividsolutions.jump.feature.FeatureCollection; import com.vividsolutions.jump.util.Blackboard; import com.vividsolutions.jump.workbench.ui.GUIUtil; import com.vividsolutions.jump.workbench.ui.LayerViewPanelProxy; import com.vividsolutions.jump.workbench.ui.WorkbenchFrame; import com.vividsolutions.jump.workbench.ui.renderer.style.BasicStyle; import com.vividsolutions.jump.workbench.ui.style.AbstractPalettePanel; /** * Registry of Layers in a Task. * @see Task * @see Layer */ public class LayerManager { private static int layerManagerCount = 0; private UndoableEditReceiver undoableEditReceiver = new UndoableEditReceiver(); private CoordinateSystem coordinateSystem = CoordinateSystem.UNSPECIFIED; //As we introduce threaded drawing and other threaded tasks to the //workbench, we should be careful not to create situations in which //ConcurrentModificationExceptions can occur. [Jon Aquino] //Go with List rather than name-to-category map, because category names can //now change. [Jon Aquino] private ArrayList categories = new ArrayList(); //Store weak references to layers rather than the layers themselves -- if a layer //is lucky enough to have all its strong references released, let it dispose of //itself immediately [Jon Aquino] private ArrayList layerReferencesToDispose = new ArrayList(); private boolean firingEvents = true; private ArrayList layerListeners = new ArrayList(); private Iterator firstColors; private Blackboard blackboard = new Blackboard(); private Task task; public LayerManager() { firstColors = firstColors().iterator(); layerManagerCount++; } public LayerManager(final Task task) { this(); this.task = task; } public UndoableEditReceiver getUndoableEditReceiver() { return undoableEditReceiver; } public void deferFiringEvents(Runnable r) { boolean firingEvents = isFiringEvents(); setFiringEvents(false); try { r.run(); } finally { setFiringEvents(firingEvents); } } private Collection firstColors() { ArrayList firstColors = new ArrayList(); for (Iterator i = AbstractPalettePanel.basicStyles().iterator(); i.hasNext();) { BasicStyle basicStyle = (BasicStyle) i.next(); if (!basicStyle.isRenderingFill()) { continue; } firstColors.add(basicStyle.getFillColor()); } return firstColors; } public Color generateLayerFillColor() { //<<TODO>> Ensure that colour is not being used by another layer [Jon Aquino] Color color = firstColors.hasNext() ? (Color) firstColors.next() : new Color((int) Math.floor( Math.random() * 256), (int) Math.floor(Math.random() * 256), (int) Math.floor(Math.random() * 256)); color = new Color(color.getRed(), color.getGreen(), color.getBlue()); return color; } public Layer addLayer(String categoryName, Layer layer) { addLayerable(categoryName, layer); return layer; } public void addLayerable(String categoryName, Layerable layerable) { if (layerable instanceof Layer) { if (size() == 0 && getCoordinateSystem() == CoordinateSystem.UNSPECIFIED) { setCoordinateSystem(((Layer) layerable).getFeatureCollectionWrapper().getFeatureSchema().getCoordinateSystem()); } else { reproject((Layer) layerable, coordinateSystem); } layerReferencesToDispose.add(new WeakReference(layerable)); } addCategory(categoryName); Category cat = getCategory(categoryName); cat.add(0, layerable); //Fire metadata changed so that the visual modified markers are updated. [Jon Aquino] fireLayerChanged(layerable, LayerEventType.METADATA_CHANGED); } private void reproject(Layer layer, CoordinateSystem coordinateSystem) { try { Assert.isTrue(indexOf(layer) == -1, "If the LayerManager contained this layer, we'd need to be concerned about rolling back on an error [Jon Aquino]"); if (!Reprojector.instance().wouldChangeValues(layer.getFeatureCollectionWrapper() .getFeatureSchema() .getCoordinateSystem(), coordinateSystem)) { return; } for (Iterator i = layer.getFeatureCollectionWrapper().iterator(); i.hasNext();) { Feature feature = (Feature) i.next(); Reprojector.instance().reproject(feature.getGeometry(), layer.getFeatureCollectionWrapper().getFeatureSchema() .getCoordinateSystem(), coordinateSystem); } } finally { //Even if #isReprojectionNecessary returned false, we still need to set //the CoordinateSystem to the new value [Jon Aquino] layer.getFeatureCollectionWrapper().getFeatureSchema() .setCoordinateSystem(coordinateSystem); } } public void addCategory(String categoryName) { addCategory(categoryName, categories.size()); } public void addCategory(String categoryName, int index) { if (getCategory(categoryName) != null) { return; } Category category = new Category(); category.setLayerManager(this); //Can't fire events because this Category hasn't been added to the //LayerManager yet. [Jon Aquino] boolean firingEvents = isFiringEvents(); setFiringEvents(false); try { category.setName(categoryName); } finally { setFiringEvents(firingEvents); } categories.add(index, category); fireCategoryChanged(category, CategoryEventType.ADDED, indexOf(category)); } public Category getCategory(String name) { for (Iterator i = categories.iterator(); i.hasNext();) { Category category = (Category) i.next(); if (category.getName().equals(name)) { return category; } } return null; } public List getCategories() { return Collections.unmodifiableList(categories); } /** * @param layerName the name of the layer. A number will be appended if a layer * with the same name already exists. Set to null to automatically generate a * new name. */ public Layer addLayer(String categoryName, String layerName, FeatureCollection featureCollection) { String actualName = (layerName == null) ? "Layer" : layerName; Layer layer = new Layer(actualName, generateLayerFillColor(), featureCollection, this); addLayerable(categoryName, layer); return layer; } public Layer addOrReplaceLayer(String categoryName, String layerName, FeatureCollection featureCollection) { Layer oldLayer = getLayer(layerName); if (oldLayer != null) { remove(oldLayer); } Layer newLayer = addLayer(categoryName, layerName, featureCollection); if (oldLayer != null) { newLayer.setStyles(oldLayer.cloneStyles()); } return newLayer; } /** * @return a unique layer name based on the given name */ public String uniqueLayerName(String name) { if (!isExistingLayerableName(name)) { return name; } //<<TODO:REMOVE>> debug [Jon Aquino] if (name == "Relative Vectors") { new Throwable().printStackTrace(System.err); } int i = 2; String newName; do { newName = name + " (" + i + ")"; i++; } while (isExistingLayerableName(newName)); return newName; } private boolean isExistingLayerableName(String name) { for (Iterator i = getLayerables(Layerable.class).iterator(); i.hasNext();) { Layerable layerable = (Layerable) i.next(); if (layerable.getName().equals(name)) { return true; } } return false; } public void remove(Layerable layerable) { for (Iterator i = categories.iterator(); i.hasNext();) { Category c = (Category) i.next(); int index = c.indexOf(layerable); if (index != -1) { c.remove(layerable); fireLayerChanged(layerable, LayerEventType.REMOVED, c, index); } } } public void removeIfEmpty(Category category) { if (!category.isEmpty()) { return; } int categoryIndex = indexOf(category); categories.remove(category); fireCategoryChanged(category, CategoryEventType.REMOVED, categoryIndex); } public int indexOf(Category category) { return categories.indexOf(category); } public void fireCategoryChanged(Category category, CategoryEventType type) { fireCategoryChanged(category, type, indexOf(category)); } private void fireCategoryChanged(final Category category, final CategoryEventType type, final int categoryIndex) { if (!firingEvents) { return; } //[sstein 2.Feb.2007] old line results sometimes in ConcurrentModificationException //for (Iterator i = layerListeners.iterator(); i.hasNext();) { //[sstein 2.Feb.2007] new line by Larry for (Iterator i = new ArrayList(layerListeners).iterator(); i.hasNext();) {//LDB added final LayerListener layerListener = (LayerListener) i.next(); fireLayerEvent(new Runnable() { public void run() { layerListener.categoryChanged(new CategoryEvent( category, type, categoryIndex)); } }); } } public void fireFeaturesChanged(final Collection features, final FeatureEventType type, final Layer layer) { Assert.isTrue(type != FeatureEventType.GEOMETRY_MODIFIED); fireFeaturesChanged(features, type, layer, null); } public void fireGeometryModified(final Collection features, final Layer layer, final Collection oldFeatureClones) { Assert.isTrue(oldFeatureClones != null); fireFeaturesChanged(features, FeatureEventType.GEOMETRY_MODIFIED, layer, oldFeatureClones); } private void fireFeaturesChanged(final Collection features, final FeatureEventType type, final Layer layer, final Collection oldFeatureClones) { if (!firingEvents) { return; } //New ArrayList to avoid ConcurrentModificationException [Jon Aquino] for (Iterator i = new ArrayList(layerListeners).iterator(); i.hasNext();) { final LayerListener layerListener = (LayerListener) i.next(); fireLayerEvent(new Runnable() { public void run() { layerListener.featuresChanged(new FeatureEvent( features, type, layer, oldFeatureClones)); } }); } } private void fireLayerEvent(Runnable eventFirer) { //In general, LayerListeners are GUI components. Therefore, notify //them on the event dispatching thread.[Jon Aquino] try { GUIUtil.invokeOnEventThread(eventFirer); } catch (InterruptedException e) { Assert.shouldNeverReachHere(); } catch (InvocationTargetException e) { e.printStackTrace(System.err); Assert.shouldNeverReachHere(); } //Note that if the current thread (in which the model was changed) is not //the event dispatching thread, the model changes and the listener notifications //will be asynchronous -- for example, all the model changes might be done //before the listener notifications even begin. This may be a //problem for a threaded plug-in that does moderately complex //insertions and deletions of nodes in the layer tree (e.g. you may see //duplicate tree nodes). If you encounter this problem, try making the //model changes (e.g. #addLayer) on the event thread using //GUIUtil#invokeOnEventThread. [Jon Aquino] } /** * If layerChangeType is DELETED, layerIndex will of course be the index of * the layer in the category prior to removal (not -1). */ private void fireLayerChanged(final Layerable layerable, final LayerEventType layerChangeType, final Category category, final int layerIndex) { if (!firingEvents) { return; } //New ArrayList to avoid ConcurrentModificationException [Jon Aquino] for (Iterator i = new ArrayList(layerListeners).iterator(); i.hasNext();) { final LayerListener layerListener = (LayerListener) i.next(); fireLayerEvent(new Runnable() { public void run() { layerListener.layerChanged(new LayerEvent(layerable, layerChangeType, category, layerIndex)); } }); } } //<<TODO:DESIGN>> Most callers of #fireLayerChanged(Layer, LayerChangeType, //LayerCategory, layerIndex) can use this simpler method instead. [Jon Aquino] public void fireLayerChanged(Layerable layerable, LayerEventType type) { Category cat = getCategory(layerable); if (cat == null) { Assert.isTrue(!isFiringEvents(), "If this event is being fired because you are constructing a Layer, " + "cat will be null because you haven't yet added the Layer to the LayerManager. " + "While constructing a layer, you should set firingEvents " + "to false. (Layerable = " + layerable.getName() + ")"); return; } fireLayerChanged(layerable, type, cat, cat.indexOf(layerable)); } public void setFiringEvents(boolean firingEvents) { this.firingEvents = firingEvents; } public boolean isFiringEvents() { return firingEvents; } /** * @return an iterator over the layers, from bottom to top. Layers with * #drawingLast = true appear last. */ public Iterator reverseIterator(Class layerableClass) { ArrayList layerablesCopy = new ArrayList(getLayerables(layerableClass)); Collections.reverse(layerablesCopy); moveLayersDrawnLastToEnd(layerablesCopy); return layerablesCopy.iterator(); } private void moveLayersDrawnLastToEnd(List layerables) { ArrayList layersDrawnLast = new ArrayList(); for (Iterator i = layerables.iterator(); i.hasNext();) { Layerable layerable = (Layerable) i.next(); if (!(layerable instanceof Layer)) { continue; } Layer layer = (Layer) layerable; if (layer.isDrawingLast()) { layersDrawnLast.add(layer); i.remove(); } } layerables.addAll(layersDrawnLast); } /** * @return Layers, not all Layerables */ public Iterator iterator() { //<<TODO:PERFORMANCE>> Create an iterator that doesn't build a Collection of //Layers first (unlike #getLayers) [Jon Aquino] return getLayers().iterator(); } /** * @return null if there is no such layer */ public Layer getLayer(String name) { for (Iterator i = iterator(); i.hasNext();) { Layer layer = (Layer) i.next(); if (layer.getName().equals(name)) { return layer; } } return null; } public void addLayerListener(LayerListener layerListener) { Assert.isTrue(!layerListeners.contains(layerListener)); layerListeners.add(layerListener); } public void removeLayerListener(LayerListener layerListener) { layerListeners.remove(layerListener); } public Layer getLayer(int index) { return (Layer) getLayers().get(index); } public int size() { return getLayers().size(); } //<<TODO:MAINTAINABILITY>> Search for uses of #getLayerListModel and see if //they can be replaced by #iterator [Jon Aquino] public Envelope getEnvelopeOfAllLayers() { return getEnvelopeOfAllLayers(false); } /** * @param visibleLayersOnly * @return the envelope containing all layers */ public Envelope getEnvelopeOfAllLayers(boolean visibleLayersOnly) { Envelope envelope = new Envelope(); for (Iterator i = iterator(); i.hasNext();) { Layer layer = (Layer) i.next(); if (visibleLayersOnly && !layer.isVisible()) { continue; } envelope.expandToInclude(layer.getFeatureCollectionWrapper() .getEnvelope()); } return envelope; } /** * @return -1 if the layer does not exist */ public int indexOf(Layer layer) { return getLayers().indexOf(layer); } public Category getCategory(Layerable layerable) { for (Iterator i = categories.iterator(); i.hasNext();) { Category category = (Category) i.next(); if (category.contains(layerable)) { return category; } } return null; } public List getLayers() { return getLayerables(Layer.class); } /** * To get all Layerables, set layerableClass to Layerable.class. */ public List getLayerables(Class layerableClass) { Assert.isTrue(Layerable.class.isAssignableFrom(layerableClass)); ArrayList layers = new ArrayList(); //Create new ArrayLists to avoid ConcurrentModificationExceptions. [Jon Aquino] for (Iterator i = new ArrayList(categories).iterator(); i.hasNext();) { Category c = (Category) i.next(); for (Iterator j = new ArrayList(c.getLayerables()).iterator(); j.hasNext();) { Layerable l = (Layerable) j.next(); if (!(layerableClass.isInstance(l))) { continue; } layers.add(l); } } return layers; } public List getVisibleLayers(boolean includeFence) { ArrayList visibleLayers = new ArrayList(getLayers()); for (Iterator i = visibleLayers.iterator(); i.hasNext();) { Layer layer = (Layer) i.next(); if (layer.getName().equals(FenceLayerFinder.LAYER_NAME) && !includeFence) { i.remove(); continue; } if (!layer.isVisible()) { i.remove(); } } return visibleLayers; } /** * The old method dispose(Layerable layerable) is deprecated. * It has been replaced by dispose(WorkbenchFrame frame, Layerable layerable) * I add it again for compatibility issues with some older plugins (Pirol's * raster PlugIn) * I'll remove this method as soon as no more plugin use it * @deprecated */ public void dispose(Layerable layerable) { for (Iterator i = categories.iterator(); i.hasNext();) { Category c = (Category) i.next(); // deleting the layer from the category int index = c.indexOf(layerable); if (index != -1) { c.remove(layerable); for (Iterator j = layerReferencesToDispose.iterator(); j.hasNext();) { WeakReference reference = (WeakReference) j.next(); Layer layer = (Layer) reference.get(); if (layer == layerable) { // removing the reference to layer layer.dispose(); layerManagerCount--; } } // changing appearance of layer tree fireLayerChanged(layerable, LayerEventType.REMOVED, c, index); } } } /** * SIGLE [obedel] on 2005 then [mmichaud] on 2007-05-22 * To free the memory allocated for a layer * Called by RemoveSelectedLayersPlugin * @param frame the worbench frame * @param layerable the layerable to remove */ public void dispose(WorkbenchFrame frame, Layerable layerable) { // removing all LayerRenderers for this Layer JInternalFrame[] internalFrames = frame.getInternalFrames(); for (int i = 0 ; i < internalFrames.length ; i++) { JInternalFrame internalFrame = internalFrames[i]; if (internalFrame instanceof LayerViewPanelProxy) { ((LayerViewPanelProxy)internalFrame).getLayerViewPanel() .getRenderingManager() .removeLayerRenderer(layerable); } //layerViewPanel.getRenderingManager().removeLayerRenderer(layerable); } for (Iterator i = categories.iterator(); i.hasNext();) { Category c = (Category) i.next(); // deleting the layer from the category int index = c.indexOf(layerable); if (index != -1) { c.remove(layerable); for (Iterator j = layerReferencesToDispose.iterator(); j.hasNext();) { WeakReference reference = (WeakReference) j.next(); Layer layer = (Layer) reference.get(); if (layer == layerable) { // removing the reference to layer layer.dispose(); layerManagerCount--; } } // changing appearance of layer tree fireLayerChanged(layerable, LayerEventType.REMOVED, c, index); } } } public void dispose() { for (Iterator i = layerReferencesToDispose.iterator(); i.hasNext();) { WeakReference reference = (WeakReference) i.next(); Layer layer = (Layer) reference.get(); if (layer != null) { layer.dispose(); } } layerManagerCount--; //Undo actions may be holding on to expensive resources; therefore, send //#die to each to request that the resources be freed. [Jon Aquino] undoableEditReceiver.getUndoManager().discardAllEdits(); } public static int layerManagerCount() { return layerManagerCount; } /** * Editability is not enforced; all parties are responsible for heeding the * editability of a layer. */ public Collection getEditableLayers() { ArrayList editableLayers = new ArrayList(); for (Iterator i = getLayers().iterator(); i.hasNext();) { Layer layer = (Layer) i.next(); if (layer.isEditable()) { editableLayers.add(layer); } } return editableLayers; } public Blackboard getBlackboard() { return blackboard; } public Collection getLayersWithModifiedFeatureCollections() { ArrayList layersWithModifiedFeatureCollections = new ArrayList(); for (Iterator i = iterator(); i.hasNext();) { Layer layer = (Layer) i.next(); if (layer.isFeatureCollectionModified()) { layersWithModifiedFeatureCollections.add(layer); } } return layersWithModifiedFeatureCollections; } public LinkedList getLayersWithNullDataSource(){ LinkedList list = new LinkedList(); for (Iterator i = iterator(); i.hasNext();) { Layer layer = (Layer) i.next(); if(layer.getDataSourceQuery() == null) { list.add(layer); } } return list; } public void setCoordinateSystem(CoordinateSystem coordinateSystem) { this.coordinateSystem = coordinateSystem; //Don't automatically reproject layers here, because I'd like to use an //EditTransaction, but that requires a LayerViewPanelContext, which is not //available to this LayerManager (but would be available to a plug-in) [Jon Aquino] } public static void main(String[] args) throws ParseException { System.out.println(Line2D.linesIntersect(708248.882609455, 2402253.07294874, 708249.523621829, 2402244.3124463, 708247.896591321, 2402252.48269854, 708261.854734465, 2402182.39086576)); System.out.println(new WKTReader().read( "LINESTRING(708248.882609455 2402253.07294874, 708249.523621829 2402244.3124463)") .intersects(new WKTReader().read( "LINESTRING(708247.896591321 2402252.48269854, 708261.854734465 2402182.39086576)"))); } public CoordinateSystem getCoordinateSystem() { return coordinateSystem; } //[UT] 25.08.2005 added public void fireFeaturesAttChanged(final Collection features, final FeatureEventType type, final Layer layer, final Collection oldFeatureClones) { Assert.isTrue(type != FeatureEventType.GEOMETRY_MODIFIED); fireFeaturesChanged(features, type, layer, oldFeatureClones); } public Task getTask() { return task; } }