/*
* GeoSet.java
*
* Created on 5. Februar 2005, 16:48
*/
package ika.geo;
import java.io.*;
import java.awt.geom.*;
import java.util.*;
/**
* GeoSet - an ordered group of GeoObjects.<br>
* @author Bernhard Jenny, Institute of Cartography, ETH Zurich.
*/
public class GeoSet extends GeoObject implements Serializable, Cloneable {
private static final long serialVersionUID = -8029643397815392824L;
/**
* A vector that contains the GeoObjects pertaining to this GeoSet.
*/
private java.util.Vector vector = new java.util.Vector();
private boolean grouped = false;
/** Creates a new instance of GeoSet */
public GeoSet() {
}
/**
* Creates a new instance of GeoSet and adds the passed GeoObject as a child
* to the new GeoSet.
* @param child A GeoObject that will be added to the new GeoSet.
*/
public GeoSet(GeoObject child) {
this.add(child);
}
/**
* Returns a copy of this GeoObject. Clones the tree of this GeoSet.
* @return A copy.
*/
@Override
public GeoSet clone() {
try {
GeoSet copy = (GeoSet)super.clone();
// clone all children in this GeoSet and add them to the copy
copy.vector = new Vector(this.vector.size());
final int nbrChildren = this.getNumberOfChildren();
for (int i = 0; i < nbrChildren; i++) {
GeoObject geoObject = this.getGeoObject(i);
copy.add(geoObject.clone());
}
return copy;
} catch (Exception exc) {
return null;
}
}
/**
* Copy all selected children to the passed GeoSet.
* @param geoSet The GeoSet that will receive the copied GeoObjects.
*/
@Override
public void cloneIfSelected(GeoSet geoSet) {
// no trigger here, since we are only reading from this GeoSet.
if (this.hasSelectedGeoObjects() == false)
return;
GeoSet newGeoSet = new GeoSet();
geoSet.add(newGeoSet);
Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
final GeoObject geoObject = (GeoObject)iterator.next();
geoObject.cloneIfSelected(newGeoSet);
}
}
/**
* Add a GeoObject to this GeoSet. The object will be inserted after all currently
* contained GeoObjects.
* @param geoObject The GeoObject to add.
*/
public synchronized void add(GeoObject geoObject) {
this.add(this.vector.size(), geoObject);
}
/**
* Deselect all GeoObjects contained in this GeoSet and insert the passed
* GeoObject. The object will be inserted after all currently
* contained GeoObjects.
*/
public synchronized void deselectAndAdd(GeoObject geoObject) {
final MapEventTrigger trigger = new MapEventTrigger(this);
try {
this.setSelected(false);
this.add(this.vector.size(), geoObject);
} finally {
trigger.inform(new MapEvent(true, true, true));
}
}
/**
* Deselect all GeoObjects contained in this GeoSet and insert the children
* of the passed GeoSet. The passed GeoSet itself is not added.
* The children will be inserted after all currently contained GeoObjects.
*/
public synchronized void deselectAndAddChildren(GeoSet geoSet) {
final MapEventTrigger trigger = new MapEventTrigger(this);
try {
this.setSelected(false);
final int nbrChildren = geoSet.getNumberOfChildren();
for (int i = 0; i < nbrChildren; i++) {
this.add(geoSet.getGeoObject(i));
}
} finally {
trigger.inform(new MapEvent(true, true, true));
}
}
/**
* Add a GeoObject at a specified index.
* @param index The position in the internal store, starting with 0.
* @param geoObject The GeoObject to add.
*/
public synchronized void add(int index, GeoObject geoObject) {
if (geoObject == null)
return;
// apply selectable state on the new child
if (!this.isSelectable())
geoObject.setSelectable(false);
vector.add(index, geoObject);
geoObject.setParent(this);
MapEventTrigger.inform(MapEvent.structureChange(), this);
}
/**
* Replaces a GeoObject by another GeoObject. If geoObjectToReplace
* cannot be found, the newGeoObject is added to this GeoSet after all
* other GeoObjects. The newGeoObject has the same values for visible and
* selected. Selectable, name and id are not changed.
* @param newGeoObject The GeoObject that will replace another one.
* @param geoObjectToReplace The GeoObject that will be removed.
*/
public synchronized void replaceGeoObject(GeoObject newGeoObject,
GeoObject geoObjectToReplace) {
final int id = this.getIndexOfGeoObject(geoObjectToReplace);
this.replaceGeoObject(newGeoObject, id);
}
/**
* Replaces a GeoObject by another GeoObject. If the object to replace
* cannot be found, the newGeoObject is added to this GeoSet after all
* other GeoObjects. The newGeoObject has the same values for visible and
* selected. Selectable, name and id are not changed.
* @param newGeoObject The GeoObject that will replace another one.
* @param index The index of the GeoObject that will be removed.
*/
public synchronized void replaceGeoObject(GeoObject newGeoObject, int index) {
final MapEventTrigger trigger = new MapEventTrigger(this);
try {
if (index >= 0 && index < this.getNumberOfChildren()) {
GeoObject removedObj = this.remove(index);
if (removedObj != null) {
newGeoObject.setSelected(removedObj.isSelected());
newGeoObject.setVisible(removedObj.isVisible());
}
this.add(index, newGeoObject);
} else {
this.add(newGeoObject);
}
} finally {
trigger.inform(new MapEvent(true, true, true));
}
}
/**
* Replaces a GeoObject by another GeoObject. If an object with the specified name
* cannot be found, the newGeoObject is added to this GeoSet after all
* other GeoObjects. The newGeoObject has the same values for visible and
* selected. Selectable, name and id are not changed.
* @param newGeoObject The GeoObject that will replace another one.
* @param name The name of the GeoObject that will be removed.
*/
public synchronized void replaceGeoObject(GeoObject newGeoObject, String name) {
final int id = this.getIndexForName(name);
this.replaceGeoObject(newGeoObject, id);
}
/**
* Removes all currently contained GeoObjects and copies references of
* the GeoObjects contained by the passed GeoSet.
*/
public synchronized void replaceGeoObjects(GeoSet geoSet) {
final boolean hasSelected = this.hasSelectedGeoObjects();
MapEventTrigger trigger = new MapEventTrigger(this);
try {
this.removeAllGeoObjects();
if (geoSet == null)
return;
final int nbrObjects = geoSet.getNumberOfChildren();
for (int i = 0; i < nbrObjects; i++) {
GeoObject obj = geoSet.getGeoObject(i);
this.add(obj);
}
} finally {
trigger.inform(new MapEvent(true, hasSelected, true));
}
}
/**
* Remove all currently contained GeoObjects from this GeoSet.
*/
public synchronized void removeAllGeoObjects() {
final boolean hasSelected = this.hasSelectedGeoObjects();
MapEventTrigger trigger = new MapEventTrigger(this);
try {
Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
final GeoObject geoObject = (GeoObject)iterator.next();
geoObject.setParent(null);
if (geoObject instanceof GeoSet) {
final GeoSet geoSet = (GeoSet)geoObject;
geoSet.removeAllGeoObjects();
}
}
vector.clear();
} finally {
trigger.inform(new MapEvent(true, hasSelected, true));
}
}
public synchronized void remove(GeoObject geoObject) {
if (geoObject == null)
return;
int index = vector.indexOf(geoObject);
if (index == -1)
return;
vector.remove(index);
geoObject.setParent(null);
MapEventTrigger.inform(new MapEvent(true, geoObject.isSelected(), false), this);
}
/**
* Removes a GeoObject at the specified index.
* @param index The position of the object to remove.
* @return The removed object.
*/
public synchronized GeoObject remove(int index) {
GeoObject geoObject = (GeoObject)vector.get(index);
if (geoObject == null)
return null;
vector.remove(index);
geoObject.setParent(null);
MapEventTrigger.inform(new MapEvent(true, geoObject.isSelected(), false), this);
return geoObject;
}
/**
* Remove all currently selected GeoObjects from this GeoSet.
*/
public synchronized boolean removeSelectedGeoObjects() {
boolean foundSelected = false;
MapEventTrigger trigger = new MapEventTrigger(this);
try {
for (int i = vector.size() - 1; i >= 0; i--) {
GeoObject geoObject = (GeoObject) (vector.get(i));
if (geoObject instanceof GeoSet) {
foundSelected |= ((GeoSet) geoObject).removeSelectedGeoObjects();
}
if (geoObject.isSelected()) {
vector.remove(i);
geoObject.setParent(null);
if (geoObject instanceof GeoSet) {
final GeoSet geoSet = (GeoSet) geoObject;
}
foundSelected = true;
}
}
return foundSelected;
} finally {
if (foundSelected) {
trigger.inform(MapEvent.structureChange());
} else {
trigger.abort();
}
}
}
/**
* Remove all objects that have a specified name.
* A map event is triggered if an object is removed.
* THIS HAS NOT BEEN STRESS-TESTED!!! ???
* @param name The name identifying objects to be removed. If null, nothing is removed.
* @return True if an object has been removed.
*/
public synchronized boolean removeByName(String name) {
if (name == null)
return false;
boolean removedObject = false;
MapEventTrigger trigger = new MapEventTrigger(this);
try {
for (int i = vector.size() - 1; i >= 0; i--) {
GeoObject geoObject = (GeoObject)(vector.get(i));
if (name.equals(geoObject.getName())) {
vector.remove(i);
geoObject.setParent(null);
removedObject = true;
} else {
if (geoObject instanceof GeoSet)
removedObject |= ((GeoSet)geoObject).removeByName(name);
}
}
return removedObject;
} finally {
if (removedObject)
trigger.inform(MapEvent.structureChange());
else
trigger.abort();
}
}
/**
* Returns a bounding box in world coordinates.
*/
public java.awt.geom.Rectangle2D getBounds2D(double scale) {
return this.getBounds2D(scale, false, false);
}
/**
* Returns the bounding box of all GeoObjects contained by this GeoSet.
* @param onlyVisible If true, only the bounding box of the currently
* visible GeoObjects is returned.
*/
public synchronized java.awt.geom.Rectangle2D getBounds2D(
double scale,
boolean onlyVisible,
boolean onlySelected) {
if (vector.size() == 0)
return null;
// search through children for first object with valid bounding box
Rectangle2D rect = null;
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext() && rect == null) {
final GeoObject geoObject = (GeoObject)iterator.next();
rect = geoObject.getBounds2D(scale, onlyVisible, onlySelected);
if (!ika.utils.GeometryUtils.isRectangleValid(rect))
rect = null;
}
if (rect == null)
return null;
rect = (Rectangle2D)rect.clone();
// compute union with bounding boxes of all following objects
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
final Rectangle2D objBounds =
geoObject.getBounds2D(scale, onlyVisible, onlySelected);
if (objBounds != null && ika.utils.GeometryUtils.isRectangleValid(objBounds))
Rectangle2D.union(rect, objBounds, rect);
}
return rect;
}
/**
* Required by abstract super class GeoObject. This implementation returns
* alway false, since a GeoSet does not have its own geometry.
*/
public boolean isPointOnSymbol(Point2D point, double tolDist, double scale) {
return false;
}
/**
* Required by abstract super class GeoObject. This implementation returns
* alway false, since a GeoSet does not have its own geometry.
*/
public boolean isIntersectedByRectangle(Rectangle2D rect, double scale) {
return false;
}
/**
* Returns the visually top-most object contained in this GeoSet that is under
* a passed point.
* @param point The point for hit detection.
* @param tolDist The tolerance to use for hit detection in world coordinates.
* @param scale The current scale of the map.
* @return Returns the GeoObject if any, null otherwise.
*/
public synchronized GeoObject getObjectAtPosition(Point2D point, double tolDist,
double scale,
boolean onlySelectable,
boolean onlyVisible) {
// search in inverse order
for (int i = vector.size() - 1; i >= 0; i--) {
final GeoObject geoObject = (GeoObject)vector.get(i);
// test if point is on symbolized GeoObject
final GeoObject geoObjectAtPosition =
geoObject.getObjectAtPosition(point, tolDist, scale,
onlySelectable, onlyVisible);
if (geoObjectAtPosition != null)
return geoObjectAtPosition;
}
return null;
}
private boolean selectByPointForUngrouped(Point2D point, double scale,
boolean extendSelection, double tolDist) {
// find object at position
final GeoObject geoObjectAtPosition =
this.getObjectAtPosition(point, tolDist, scale, true, true);
boolean selectionChanged = false;
if (geoObjectAtPosition != null) {
selectionChanged = (geoObjectAtPosition.isSelected() == false);
// deselect all currently selected objects
this.setSelected(false);
// select the found object (it has been deselected in the line above)
geoObjectAtPosition.setSelected(true);
} else {
selectionChanged = this.hasSelectedGeoObjects();
this.setSelected(false);
}
return selectionChanged;
}
private boolean selectByPointForGrouped(Point2D point, double scale,
boolean extendSelection, double tolDist) {
// remember if the selection state of any child changed
boolean selectionChanged = false;
boolean objectHit = false;
for (int i = this.vector.size() - 1; i >= 0; i--) {
final GeoObject geoObject = (GeoObject)this.vector.get(i);
if (!geoObject.isVisible())
continue;
objectHit = geoObject.isPointOnSymbol(point, tolDist, scale);
if (objectHit)
break;
}
final boolean select;
final GeoObject firstGeoObject = (GeoObject)this.vector.get(0);
if (objectHit) {
if (extendSelection) {
select = !firstGeoObject.isSelected();
selectionChanged = true;
} else {
select = true;
selectionChanged = !firstGeoObject.isSelected();
}
} else {
select = false;
selectionChanged = firstGeoObject.isSelected();
}
this.setSelected(select);
return selectionChanged;
}
public synchronized boolean selectByPoint(Point2D point, double scale,
boolean extendSelection, double tolDist) {
if (this.vector.size() == 0 || !this.isVisible())
return false;
boolean selectionChanged = false;
MapEventTrigger trigger = new MapEventTrigger(this);
try {
if (this.grouped) {
selectionChanged =
this.selectByPointForGrouped(point, scale, extendSelection, tolDist);
} else {
selectionChanged =
this.selectByPointForUngrouped(point, scale, extendSelection, tolDist);
}
return selectionChanged;
} finally {
if (selectionChanged)
trigger.inform(MapEvent.selectionChange());
else
trigger.abort();
}
}
/**
* Selects all GeoObjects contained by this GeoSet that intersect with the
* passed rectangle.
*/
public synchronized boolean selectByRectangle(Rectangle2D rect, double scale,
boolean extendSelection){
if (this.vector.size() == 0)
return false;
boolean selectionChanged = false;
MapEventTrigger trigger = new MapEventTrigger(this);
try {
if (this.grouped) {
// this is a group, test if rectangle hits any child.
boolean objectHit = false;
for (int i = this.vector.size() - 1; i >= 0; i--) {
final GeoObject geoObject = (GeoObject)this.vector.get(i);
objectHit = geoObject.isIntersectedByRectangle(rect, scale);
if (objectHit)
break;
}
final boolean select;
final GeoObject firstGeoObject = (GeoObject)this.vector.get(0);
if (objectHit) {
if (extendSelection) {
select = !firstGeoObject.isSelected();
selectionChanged = true;
} else {
select = true;
selectionChanged = !firstGeoObject.isSelected();
}
} else {
select = false;
selectionChanged = firstGeoObject.isSelected();
}
this.setSelected(select);
} else {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
final GeoObject geoObject = (GeoObject)iterator.next();
selectionChanged |= geoObject.selectByRectangle(rect, scale, extendSelection);
}
}
return selectionChanged;
} finally {
if (selectionChanged)
trigger.inform(MapEvent.selectionChange());
else
trigger.abort();
}
}
/**
* Overwrite GeoObject's setSelected method. The new selection state is
* applied to each GeoObject contained by this GeoSet.
* @param selected The new selection state of all GeoObjects contained by
* this GeoSet.
*/
public synchronized void setSelected(boolean selected) {
MapEventTrigger trigger = new MapEventTrigger(this);
try {
// call the overwritten method to select this GeoSet
super.setSelected(selected);
// pass the selection state to all children
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
geoObject.setSelected(selected);
}
} finally {
trigger.inform(MapEvent.selectionChange());
}
}
/**
* Overwrite GeoObject's setSelectable method. The new selectable state is
* applied to each GeoObject contained by this GeoSet.
* @param selectable The new selectable state of all GeoObjects contained by
* this GeoSet.
*/
public synchronized void setSelectable(boolean selectable) {
MapEventTrigger trigger = new MapEventTrigger(this);
try {
// call the overwritten method to select this GeoSet
super.setSelectable(selectable);
// pass the selection state to all children
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
geoObject.setSelectable(selectable);
}
} finally {
trigger.inform(MapEvent.selectionChange());
}
}
/** Assign a shared VectorSymbol to all GeoPath in this GeoSet and all
* sub-GeoSets.
* @param vectorSymbol The shared instance of a VectorSymbol that will be
* assigned to all children GeoPaths.
*/
public synchronized void setVectorSymbol(VectorSymbol vectorSymbol){
MapEventTrigger trigger = new MapEventTrigger(this);
try {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
if (obj instanceof GeoPath)
((GeoPath)obj).setVectorSymbol(vectorSymbol);
else if (obj instanceof GeoSet)
((GeoSet)obj).setVectorSymbol(vectorSymbol);
}
} finally {
trigger.inform();
}
}
/**
* Returns the number of GeoObjects contained by this GeoSet.
* @return The number of GeoObjects contained by this GeoSet.
*/
public synchronized int getNumberOfChildren() {
return this.vector.size();
}
/**
* Returns the number of GeoSets that are direct children of this GeoSet.
* GeoSets further down the tree are not counted.
* @return The number of GeoSets contained by this GeoSet.
*/
public synchronized int getNumberOfSubSets() {
int numberOfSubSets = 0;
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
if (geoObject instanceof GeoSet)
numberOfSubSets ++;
}
return numberOfSubSets;
}
/**
* Returns the number of GeoSets contained in the tree below this GeoSet.
* @return The number of GeoSets contained by this GeoSet.
*/
public synchronized int getNumberOfSubSetsInTree() {
int numberOfSubSets = 0;
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
Object geoObject = iterator.next();
if (geoObject instanceof GeoSet)
numberOfSubSets += ((GeoSet)geoObject).getNumberOfSubSetsInTree() + 1;
}
return numberOfSubSets;
}
/**
* Returns the GeoObject at a certain index.
*/
public synchronized GeoObject getGeoObject(int id) {
return (GeoObject)this.vector.get(id);
}
public synchronized Object[] getGeoObjectsAsArray() {
return this.vector.toArray();
}
/**
* Returns the first GeoObject with an ID. Note that IDs need not to be unique.
*/
public synchronized GeoObject getGeoObjectByID(long id) {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
if (geoObject.getID() == id)
return geoObject;
}
return null;
}
/**
* Returns this GeoSet if the passed name is the name of this GeoSet.
* Otherwise searches through all children for a GeoObject with this name.
* Uses a depth-first search.
* @param name The name of the GeoObject that is being searched.
* @return This GeoSet or the first child with the passed name.
*/
public synchronized GeoObject getGeoObject(String name) {
if (name.equals(this.getName()))
return this;
Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
GeoObject foundGeoObject = geoObject.getGeoObject(name);
if (foundGeoObject != null)
return foundGeoObject;
}
return null;
}
/**
*
* @return The position of the passed object in the array that stores the
* GeoObjects of this GeoSet. Returns -1 if the object is not found.
*/
public synchronized int getIndexOfGeoObject(Object obj) {
if (obj == null)
return -1;
return this.vector.indexOf(obj);
}
/**
*
* @return The position of the object with the passed name in the array that stores the
* GeoObjects of this GeoSet. Returns -1 if the object is not found.
*/
public synchronized int getIndexForName(String name) {
int id = 0;
Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
GeoObject foundGeoObject = geoObject.getGeoObject(name);
if (foundGeoObject != null) {
return id;
}
++id;
}
return -1;
}
public synchronized void drawNormalState(RenderParams rp) {
if (this.isVisible()) {
final java.util.Iterator iterator = vector.iterator();
while (iterator.hasNext()) {
final GeoObject geoObject = (GeoObject)iterator.next();
if (geoObject.isVisible()) {
geoObject.drawNormalState(rp);
}
}
}
}
public synchronized void drawSelectedState(RenderParams rp) {
if (this.isVisible()) {
final java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
final GeoObject geoObject = (GeoObject)iterator.next();
if (geoObject.isVisible()) {
geoObject.drawSelectedState(rp);
}
}
}
}
/**
* Returns true if this GeoSet contains any GeoObject that is currently selected.
*/
public synchronized boolean hasSelectedGeoObjects() {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
if (geoObject instanceof GeoSet) {
GeoSet geoSet = (GeoSet)geoObject;
if (geoSet.hasSelectedGeoObjects())
return true;
} else {
if (geoObject.isSelected())
return true;
}
}
return false;
}
/**
* Returns true if this GeoSet contains any GeoObject that is currently visible.
* Returns false if the GeoSet itself is not visible.
* Returns false if the GeoSet does not contain any children.
* This traverses all children and children of children until a visible
* GeoObject is found.
*/
public synchronized boolean hasVisibleGeoObjects() {
if (!this.isVisible())
return false;
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
if (geoObject instanceof GeoSet) {
if (((GeoSet)geoObject).hasVisibleGeoObjects())
return true;
} else {
if (geoObject.isVisible())
return true;
}
}
return false;
}
private class SingleSelectionSearcher {
private GeoObject foundSelectedObj = null;
private boolean moreThanOneSelected = false;
public SingleSelectionSearcher(GeoSet geoSet) {
this.search(geoSet);
}
private void search(GeoSet geoSet) {
java.util.Iterator iterator = geoSet.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
if (geoObject instanceof GeoSet && !((GeoSet)geoObject).isGrouped()) {
this.search((GeoSet)geoObject);
} else if (geoObject.isSelected()) {
if (this.foundSelectedObj != null) {
this.moreThanOneSelected = true;
} else {
this.foundSelectedObj = geoObject;
}
}
if (this.moreThanOneSelected)
break;
}
}
public GeoObject getSingleSelectedGeoObject() {
if(foundSelectedObj != null && moreThanOneSelected == false)
return foundSelectedObj;
else
return null;
}
}
/**
* Private helper method. Returns the selected GeoObject, if there is
* exactly one selected, returns null otherwise.
* Abuses excpetions. Should be done in a nicer and better way. !!! ???
*/
private synchronized GeoObject searchSingleSelectedGeoObject(
GeoObject selectedObj, boolean searchChildren) throws Exception {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
if (searchChildren
&& geoObject instanceof GeoSet
&& !((GeoSet)geoObject).isGrouped()) {
GeoSet geoSet = (GeoSet)geoObject;
GeoObject selectedObjOfSubSet =
geoSet.searchSingleSelectedGeoObject(selectedObj, searchChildren);
if (selectedObjOfSubSet != null) {
if (selectedObj != null)
throw new Exception();
else
selectedObj = selectedObjOfSubSet;
}
} else {
if (geoObject.isSelected()) {
if (selectedObj != null)
throw new Exception();
selectedObj = geoObject;
}
}
}
return selectedObj;
}
/**
* Returns the selected GeoObject, if there is exactly one selected one,
* returns null otherwise.
* A grouped GeoSet is considered to be a single object.
*/
public synchronized GeoObject getSingleSelectedGeoObject(boolean searchChildren) {
return new SingleSelectionSearcher(this).getSingleSelectedGeoObject();
}
public GeoObject getSingleSelectedGeoObject() {
return this.getSingleSelectedGeoObject(true);
}
/**
* Returns the selected GeoObject, if there is exactly one selected object
* of the required class or of a sublcass, returns null otherwise.
* A grouped GeoSet is considered to be a single object.
*
*/
public synchronized GeoObject getSingleSelectedGeoObject(Class requiredClass,
boolean searchChildren) {
GeoObject geoObject =
new SingleSelectionSearcher(this).getSingleSelectedGeoObject();
if (geoObject != null && requiredClass.isInstance(geoObject))
return geoObject;
return null;
}
public GeoObject getSingleSelectedGeoObject(Class requiredClass) {
return this.getSingleSelectedGeoObject(requiredClass, true);
}
/**
* Returns all GeoObject of a certain class or its subclasses.
*/
public synchronized void getAllGeoObjects(Class cl,
Collection foundGeoObjects,
boolean onlySelected) {
try {
// do a scan among all children of this GeoSet
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
if (cl.isInstance(obj)) {
if (onlySelected) {
if (((GeoObject)obj).isSelected())
foundGeoObjects.add(obj);
} else {
foundGeoObjects.add(obj);
}
}
}
// ask children for selected objects
iterator = this.vector.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
if (GeoSet.class.isInstance(obj)) {
final GeoSet geoSet = (GeoSet)obj;
geoSet.getAllGeoObjects(cl, foundGeoObjects, onlySelected);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Returns the first GeoObject of a specified class, or the first GeoObject
* that is not of the specified class. The search can optionally be limited
* to selected objects.
* @param requiredClass The GeoObject must be of this class, unless
* exclusive is true.
* @param exclusive If true, the first GeoObject that is not of the
* requiredClass is returned, otherwise the first GeoObject that is of the
* specified class is returned.
* @param requireSelected The GeoObject must be selected.
* @return The first GeoObject in this GeoSet or in one of its sub-GeoSets
* that is of the specified class.
*/
public synchronized GeoObject getFirstGeoObject(Class requiredClass,
boolean exclusive, boolean requireSelected) {
try {
// do a scan among all children of this GeoSet
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
final Object obj = iterator.next();
final boolean sameClass = requiredClass.isInstance(obj);
if (sameClass ^ exclusive) {
if (!requireSelected || (requireSelected && ((GeoObject)obj).isSelected()))
return (GeoObject)obj;
}
}
// ask child GeoSets for the object
iterator = this.vector.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
if (obj instanceof GeoSet) {
GeoObject childGeoObject = ((GeoSet)obj).getFirstGeoObject(
requiredClass, exclusive, requireSelected);
if (childGeoObject != null)
return childGeoObject;
}
}
} catch (Exception e) {}
return null;
}
public synchronized void move(double dx, double dy) {
MapEventTrigger trigger = new MapEventTrigger(this);
try {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
geoObject.move(dx, dy);
}
} finally {
trigger.inform();
}
}
public synchronized void rotate(double rotRad) {
MapEventTrigger trigger = new MapEventTrigger(this);
try {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
geoObject.rotate(rotRad);
}
} finally {
trigger.inform();
}
}
public synchronized void transform(AffineTransform affineTransform) {
MapEventTrigger trigger = new MapEventTrigger(this);
try {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
GeoObject geoObject = (GeoObject)iterator.next();
geoObject.transform(affineTransform);
}
} finally {
trigger.inform();
}
}
/**
* Transform all selected GeoObjects contained by this GeoSet.
*/
public synchronized boolean transformSelected(
AffineTransform affineTransform) {
boolean transformedChild = false;
MapEventTrigger trigger = new MapEventTrigger(this);
try {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
final GeoObject geoObject = (GeoObject)iterator.next();
transformedChild |= geoObject.transformSelected(affineTransform);
}
return transformedChild;
} finally {
if (transformedChild)
trigger.inform();
else
trigger.abort();
}
}
/**
* Moves all selected GeoObjects contained by this GeoSet by a certain distance.
* @return Returns true if there was any object moved.
*/
public synchronized boolean moveSelected(double dx, double dy) {
boolean movedChild = false;
MapEventTrigger trigger = new MapEventTrigger(this);
try {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
final GeoObject geoObject = (GeoObject)iterator.next();
movedChild |= geoObject.moveSelected(dx, dy);
}
return movedChild;
} finally {
if (movedChild)
trigger.inform();
else
trigger.abort();
}
}
public synchronized boolean cloneAndMoveSelected(double dx, double dy) {
boolean foundSelected = false;
MapEventTrigger trigger = new MapEventTrigger(this);
try {
// can't use an iterator, since the cloned objects will be appended to
// the vector of this GeoSet
final int nbrInitialChildren = this.getNumberOfChildren();
for (int i = 0; i < nbrInitialChildren; i++) {
final GeoObject geoObject = (GeoObject)this.vector.get(i);
foundSelected |= geoObject.cloneAndMoveSelected(dx, dy);
}
return foundSelected;
} finally {
trigger.inform();
}
}
public void scale(double scale) {
this.scale(scale, scale);
}
/**
* Scale this GeoObject horizontally and vertically.
* Attention: not all derived classes support scaling, or uneven scaling.
* @param hScale The horizontal scale factor.
* @param vScale The vertical scale factor.
*/
public synchronized void scale(double hScale, double vScale) {
MapEventTrigger trigger = new MapEventTrigger(this);
try {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()){
GeoObject geoObject = (GeoObject)iterator.next();
geoObject.scale(hScale, vScale);
}
} finally {
trigger.inform();
}
}
/**
* Scale all selected GeoObjects contained by this GeoSet.
*/
public void scaleSelected(double scale) {
this.scaleSelected(scale, scale);
}
/**
* Scales all selected GeoObjects contained by this GeoSet.
*/
public synchronized boolean scaleSelected(double hScale, double vScale) {
boolean scaledChild = false;
MapEventTrigger trigger = new MapEventTrigger(this);
try {
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
final GeoObject geoObject = (GeoObject)iterator.next();
scaledChild |= geoObject.scaleSelected(hScale, vScale);
}
return scaledChild;
} finally {
if (scaledChild)
trigger.inform();
else
trigger.abort();
}
}
/**
* Relatively deforms all selected and visible GeoObjects contained by this
* GeoSet to fit into a new bounding box.
*/
public synchronized boolean deformSelected(Rectangle2D newBounds) {
boolean changedChild = false;
MapEventTrigger trigger = new MapEventTrigger(this);
try {
Rectangle2D bounds = getBounds2D(GeoObject.UNDEFINED_SCALE, true, true);
changedChild = this.moveSelected(-bounds.getMinX(), -bounds.getMinY());
double hScale = newBounds.getWidth() / bounds.getWidth();
double vScale = newBounds.getHeight() / bounds.getHeight();
changedChild |= this.scaleSelected(hScale, vScale);
changedChild |= this.moveSelected(newBounds.getMinX(), newBounds.getMinY());
return changedChild;
} finally {
if (changedChild)
trigger.inform();
else
trigger.abort();
}
}
public synchronized boolean isGrouped() {
return grouped;
}
public synchronized void setGrouped(boolean grouped) {
this.grouped = grouped;
// propagate to children GeoSets
java.util.Iterator iterator = this.vector.iterator();
while (iterator.hasNext()) {
final GeoObject geoObject = (GeoObject)iterator.next();
if (geoObject instanceof GeoSet) {
GeoSet geoSet = (GeoSet)geoObject;
geoSet.setGrouped(grouped);
}
}
}
public synchronized ArrayList toArrayList() {
return new ArrayList(this.vector);
}
}