001: /* uDig - User Friendly Desktop Internet GIS client
002: * http://udig.refractions.net
003: * (C) 2004, Refractions Research Inc.
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation;
008: * version 2.1 of the License.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: */
015: package net.refractions.udig.tools.edit.support;
016:
017: import java.util.ArrayList;
018: import java.util.Collections;
019: import java.util.Iterator;
020: import java.util.List;
021: import java.util.concurrent.atomic.AtomicReference;
022:
023: import com.vividsolutions.jts.geom.Envelope;
024:
025: import net.refractions.udig.tools.edit.EditPlugin;
026:
027: /**
028: * Models a geometry as required by the PixelCoordMap.
029: *
030: * @author jones
031: * @since 1.1.0
032: */
033: public class EditGeom implements Iterable<PrimitiveShape> {
034:
035: private final PrimitiveShape shell;
036: private final List<PrimitiveShape> holes = Collections
037: .synchronizedList(new ArrayList<PrimitiveShape>());
038: EditBlackboard owner;
039: private final AtomicReference<ShapeType> shapeType = new AtomicReference<ShapeType>();
040: {
041: shapeType.set(ShapeType.UNKNOWN);
042: }
043: private final AtomicReference<String> featureID = new AtomicReference<String>();
044: private AtomicReference<Boolean> changed = new AtomicReference<Boolean>();
045: boolean initializing;
046:
047: /**
048: * The latest up-to-date bounds of the feature in datastore.
049: */
050: private Envelope featureBBox = null;
051:
052: /**
053: * It is recommended to use constructor with bounds.
054: *
055: * @param owner
056: * @param featureId2
057: */
058: public EditGeom(EditBlackboard owner, String featureId2) {
059: this .owner = owner;
060: shell = new PrimitiveShape(this );
061: this .featureID.set(featureId2);
062: changed.set(false);
063: }
064:
065: /**
066: *
067: *
068: * @param owner
069: * @param featureId
070: * @param featureBounds
071: */
072: public EditGeom(EditBlackboard owner, String featureId,
073: Envelope featureBounds) {
074: this .owner = owner;
075: shell = new PrimitiveShape(this );
076: this .featureID.set(featureId);
077: changed.set(false);
078: this .featureBBox = featureBounds;
079: }
080:
081: public EditGeom(EditGeom geom) {
082: shell = new PrimitiveShape(geom.getShell());
083: for (PrimitiveShape hole : geom.getHoles()) {
084: holes.add(new PrimitiveShape(hole));
085: }
086: owner = geom.owner;
087: }
088:
089: public PrimitiveShape getShell() {
090: return shell;
091: }
092:
093: /**
094: * User is expected to add holes as needed.
095: *
096: * @return
097: */
098: public List<PrimitiveShape> getHoles() {
099: return holes;
100: }
101:
102: @Override
103: public String toString() {
104: StringBuffer buffer = new StringBuffer();
105: buffer.append(featureID.get());
106: buffer.append(" "); //$NON-NLS-1$
107: buffer.append(shapeType);
108: buffer.append(" "); //$NON-NLS-1$
109: buffer.append(shell.toString());
110: buffer.append("{"); //$NON-NLS-1$
111: for (int i = 0, numHoles = this .holes.size(); i < numHoles; i++) {
112: buffer.append(holes.get(i).toString());
113: }
114: buffer.append("}"); //$NON-NLS-1$
115: return buffer.toString();
116: }
117:
118: public PrimitiveShape newHole() {
119: PrimitiveShape hole = new PrimitiveShape(this );
120: holes.add(hole);
121: return hole;
122: }
123:
124: /**
125: * @return Returns the featureID.
126: */
127: public AtomicReference<String> getFeatureIDRef() {
128: return featureID;
129: }
130:
131: /**
132: * @return Returns the shape type.
133: */
134: public ShapeType getShapeType() {
135: return shapeType.get();
136: }
137:
138: /**
139: * This is a thread-safe method
140: *
141: * @param type The new shape type.
142: */
143: public void setShapeType(ShapeType shapeType) {
144: this .shapeType.set(shapeType);
145: }
146:
147: public Iterator<PrimitiveShape> iterator() {
148: return new Iterator<PrimitiveShape>() {
149:
150: boolean shellAccessed = false;
151: Iterator<PrimitiveShape> holeIter = holes.iterator();
152:
153: public boolean hasNext() {
154: return !shellAccessed || holeIter.hasNext();
155: }
156:
157: public PrimitiveShape next() {
158: if (!shellAccessed) {
159: shellAccessed = true;
160: return shell;
161: }
162:
163: return holeIter.next();
164: }
165:
166: public void remove() {
167: throw new UnsupportedOperationException(
168: "Remove not allowed"); //$NON-NLS-1$
169: }
170:
171: };
172: }
173:
174: /**
175: * @return Returns the owner.
176: */
177: public EditBlackboard getEditBlackboard() {
178: return owner;
179: }
180:
181: /**
182: * Returns an old bounding box of the feature whose geometry is wrapped by EditGeom
183: * before any editing.
184: *
185: * @return
186: * Returns an old bounding box of the feature
187: */
188: public Envelope getFeatureEnvelope() {
189: return featureBBox;
190: }
191:
192: /**
193: * Indicates that this geometry has been modified since it has been in the blackboard.
194: *
195: * @return true if the geometry has been modified while on the blackboard.
196: */
197: public boolean isChanged() {
198: return changed.get();
199: }
200:
201: /**
202: * This method signals the in-memory state of EditGeom with respect to the actual
203: * feature in datastore.
204: * <p>
205: * <li><code>true</code> value means that the geometry is changed but not up-to-date
206: * with datastore's feature geometry.
207: * <li><code>false</code> value means that the geometry is up-to-date
208: * with datastore's feature geometry.
209: *
210: *
211: *
212: * @param changed
213: */
214: public void setChanged(boolean changed) {
215: if (this .changed.get() && !changed) {
216: synchronized (owner) {
217: Envelope envelope = new Envelope();
218: for (int i = 0; i < shell.getNumCoords(); i++) {
219: envelope.expandToInclude(shell.getCoord(i));
220: }
221: featureBBox = envelope;
222: }
223: }
224:
225: this .changed.set(changed);
226:
227: }
228:
229: public Selection createSelection() {
230: return new EditGeomSelection(this );
231: }
232:
233: public void assertValid() {
234: if (!EditPlugin.isDebugging(EditPlugin.RUN_ASSERTIONS))
235: return;
236: for (PrimitiveShape shape : this ) {
237: shape.assertValid();
238: }
239: }
240:
241: public boolean hasVertex(Point point) {
242: if (shapeType.get() == ShapeType.POLYGON) {
243: for (PrimitiveShape shape : this ) {
244: if (shape.hasVertex(point))
245: return true;
246: }
247: return false;
248: } else {
249: return shell.hasVertex(point);
250: }
251: }
252:
253: /**
254: * gets the closest edge in the geometry to the point.
255: *
256: * @param point reference point
257: * @param treatUnknownAsPolygon declares whether to treat geometries of type UNKNOWN as a polygon
258: * @return
259: */
260: public ClosestEdge getClosestEdge(Point point,
261: boolean treatUnknownAsPolygon) {
262: assert shapeType.get() != ShapeType.POINT;
263: ClosestEdge closest = shell.getClosestEdge(point,
264: treatUnknownAsPolygon);
265: if (shapeType.get() == ShapeType.POLYGON
266: || (shapeType.get() == ShapeType.UNKNOWN && treatUnknownAsPolygon)) {
267:
268: if (shell.contains(point, treatUnknownAsPolygon)) {
269: for (PrimitiveShape shape : holes) {
270: ClosestEdge current = shape.getClosestEdge(point,
271: treatUnknownAsPolygon);
272: if (closest == null)
273: closest = current;
274: else if (current != null
275: && current.distanceToEdge < closest.distanceToEdge)
276: closest = current;
277: }
278: }
279: }
280: return closest;
281: }
282:
283: public Point overVertex(Point point, int radius) {
284: for (PrimitiveShape shape : this ) {
285: Point p = shape.overVertex(point, radius);
286: if (p != null)
287: return p;
288: }
289: return null;
290: }
291:
292: }
|