0001: /* uDig - User Friendly Desktop Internet GIS client
0002: * http://udig.refractions.net
0003: * (C) 2004, Refractions Research Inc.
0004: *
0005: * This library is free software; you can redistribute it and/or
0006: * modify it under the terms of the GNU Lesser General Public
0007: * License as published by the Free Software Foundation;
0008: * version 2.1 of the License.
0009: *
0010: * This library is distributed in the hope that it will be useful,
0011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0013: * Lesser General Public License for more details.
0014: */
0015: package net.refractions.udig.tools.edit.support;
0016:
0017: import java.awt.geom.PathIterator;
0018: import java.io.IOException;
0019: import java.util.ArrayList;
0020: import java.util.Collection;
0021: import java.util.Collections;
0022: import java.util.HashSet;
0023: import java.util.LinkedList;
0024: import java.util.List;
0025: import java.util.Set;
0026:
0027: import net.refractions.udig.core.IBlockingProvider;
0028: import net.refractions.udig.mapgraphic.grid.GridMapGraphic;
0029: import net.refractions.udig.project.IBlackboard;
0030: import net.refractions.udig.project.ILayer;
0031: import net.refractions.udig.project.IMap;
0032: import net.refractions.udig.project.ProjectBlackboardConstants;
0033: import net.refractions.udig.project.command.AbstractCommand;
0034: import net.refractions.udig.project.command.UndoableComposite;
0035: import net.refractions.udig.project.command.UndoableMapCommand;
0036: import net.refractions.udig.project.ui.AnimationUpdater;
0037: import net.refractions.udig.project.ui.render.displayAdapter.ViewportPane;
0038: import net.refractions.udig.project.ui.tool.IToolContext;
0039: import net.refractions.udig.tools.edit.EditPlugin;
0040: import net.refractions.udig.tools.edit.EditState;
0041: import net.refractions.udig.tools.edit.EditToolHandler;
0042: import net.refractions.udig.tools.edit.animation.SearchBoxAnimation;
0043: import net.refractions.udig.tools.edit.commands.AddVertexCommand;
0044: import net.refractions.udig.tools.edit.commands.CreateAndSelectHoleCommand;
0045: import net.refractions.udig.tools.edit.preferences.PreferenceUtil;
0046: import net.refractions.udig.ui.ProgressManager;
0047:
0048: import org.eclipse.core.runtime.IProgressMonitor;
0049: import org.geotools.data.FeatureSource;
0050: import org.geotools.feature.Feature;
0051: import org.geotools.feature.FeatureCollection;
0052: import org.geotools.feature.FeatureIterator;
0053: import org.geotools.filter.FidFilter;
0054: import org.geotools.filter.Filter;
0055: import org.geotools.filter.FilterFactory;
0056: import org.geotools.filter.FilterFactoryFinder;
0057: import org.geotools.filter.FilterType;
0058: import org.geotools.filter.IllegalFilterException;
0059: import org.geotools.geometry.jts.JTS;
0060: import org.opengis.referencing.FactoryException;
0061: import org.opengis.referencing.operation.MathTransform;
0062: import org.opengis.referencing.operation.TransformException;
0063:
0064: import com.vividsolutions.jts.geom.Coordinate;
0065: import com.vividsolutions.jts.geom.Envelope;
0066: import com.vividsolutions.jts.geom.Geometry;
0067: import com.vividsolutions.jts.geom.LinearRing;
0068:
0069: /**
0070: * Methods for determining spatial relationships between points.
0071: *
0072: * @author Jesse
0073: * @since 1.1.0
0074: */
0075: public class EditUtils {
0076:
0077: static final int TOP = 0x8, BOTTOM = 0x4, RIGHT = 0x2, LEFT = 0x1;
0078:
0079: public static final int OVER_EDGE = -1;
0080: public static final int NO_INTERSECTION = -2;
0081:
0082: private static final String EDIT_FEATURE_BOUNDS = "BOUNDS OF RENDERING FILTER"; //$NON-NLS-1$
0083:
0084: public static EditUtils instance = new EditUtils();
0085:
0086: public int overVertext(Coordinate[] coords, Envelope env) {
0087: if (coords == null)
0088: return NO_INTERSECTION;
0089: for (int i = 0; i < coords.length; i++) {
0090: Coordinate coord = coords[i];
0091: if (env.contains(coord))
0092: return i;
0093: }
0094: return OVER_EDGE;
0095: }
0096:
0097: /**
0098: * Return true if the envelope overlaps at least one edge of the shape. This checks all the
0099: * Coordinates in the shape so the envelope must be in the "world" space (same projection as coordinates).
0100: *
0101: * @param shape to search
0102: * @param env envelope used to see if it overlaps an edge
0103: * @return true if the envelope overlaps at least one edge of the shape.
0104: */
0105: public boolean overEdgeCoordinatePrecision(PrimitiveShape shape,
0106: Envelope env) {
0107: if (shape.getNumCoords() < 2)
0108: return false;
0109: Coordinate endPoint1 = shape.getCoord(shape.getNumCoords() - 1);
0110: for (int i = 0; i < shape.getNumCoords(); i++) {
0111: Coordinate endPoint2 = shape.getCoord(i);
0112: if (overEdge(endPoint1, endPoint2, env))
0113: return true;
0114: endPoint1 = endPoint2;
0115: }
0116: return false;
0117: }
0118:
0119: /**
0120: * Return true if the envelope overlaps at least one edge of the shape. This only checks the
0121: * Points in the shape so the envelope must be in pixel space. This also means that it is not
0122: * completely accurate but it is sufficient for interactive purposes.
0123: *
0124: * @param shape to search
0125: * @param env envelope used to see if it overlaps an edge
0126: * @return true if the envelope overlaps at least one edge of the shape.
0127: */
0128: public boolean overEdgePixelPrecision(PrimitiveShape shape,
0129: Envelope env) {
0130: if (shape.getNumPoints() < 2)
0131: return false;
0132: Point point1 = shape.getPoint(shape.getNumPoints() - 1);
0133: for (int i = 0; i < shape.getNumPoints(); i++) {
0134: Point point2 = shape.getPoint(i);
0135:
0136: Coordinate endPoint1 = new Coordinate(point1.getX(), point1
0137: .getY());
0138: Coordinate endPoint2 = new Coordinate(point2.getX(), point2
0139: .getY());
0140: if (overEdge(endPoint1, endPoint2, env))
0141: return true;
0142: point1 = point2;
0143: }
0144: return false;
0145: }
0146:
0147: /**
0148: * Returns true if the envelope overlaps some part of the edge
0149: *
0150: * @param endPoint1 one end point of the edge
0151: * @param endPoint2 the other end point of the edge
0152: * @param env the reference envelope.
0153: * @return true if the envelope overlaps some part of the edge
0154: */
0155: public boolean overEdge(Coordinate endPoint1, Coordinate endPoint2,
0156: Envelope env) {
0157: boolean accept = false, done = false;
0158: double x0 = endPoint1.x;
0159: double y0 = endPoint1.y;
0160: double x1 = endPoint2.x;
0161: double y1 = endPoint2.y;
0162:
0163: double xmin = env.getMinX();
0164: double ymin = env.getMinY();
0165: double xmax = env.getMaxX();
0166: double ymax = env.getMaxY();
0167:
0168: int outcode0 = compOutCode(x0, y0, xmin, xmax, ymin, ymax);
0169: int outcode1 = compOutCode(x1, y1, xmin, xmax, ymin, ymax);
0170: do {
0171: if (outcode0 == 0 || outcode1 == 0)
0172: accept = done = true;
0173: else if ((outcode0 & outcode1) != 0)
0174: done = true;
0175: else {
0176: double x, y;
0177: int outcodeOut = outcode0 > 0 ? outcode0 : outcode1;
0178: if ((outcodeOut & TOP) > 0) {
0179: x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0);
0180: y = ymax;
0181: } else if ((outcodeOut & BOTTOM) > 0) {
0182: x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0);
0183: y = ymin;
0184: } else if ((outcodeOut & RIGHT) > 0) {
0185: y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0);
0186: x = xmax;
0187: } else {
0188: y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0);
0189: x = xmin;
0190: }
0191: if (outcodeOut == outcode0) {
0192: double tmpx = x;
0193: double tmpy = y;
0194: outcode0 = compOutCode(tmpx, tmpy, xmin, xmax,
0195: ymin, ymax);
0196: } else {
0197: double tmpx = x;
0198: double tmpy = y;
0199: outcode1 = compOutCode(tmpx, tmpy, xmin, xmax,
0200: ymin, ymax);
0201: }
0202: }
0203: } while (done == false);
0204: return accept;
0205: }
0206:
0207: private int compOutCode(double x, double y, double xmin,
0208: double xmax, double ymin, double ymax) {
0209: int outcode = 0;
0210: if (y > ymax)
0211: outcode |= TOP;
0212: if (y < ymin)
0213: outcode |= BOTTOM;
0214: if (x > xmax)
0215: outcode |= RIGHT;
0216: if (x < xmin)
0217: outcode |= LEFT;
0218: return outcode;
0219: }
0220:
0221: /**
0222: * Returns the index of the coordinate closest to the click.
0223: * @param geometry the geometry to search for the closest coordinate; The default geometry is searched.
0224: * @param click the closest coordinate in <i>coordinates</i> will be found with respect to
0225: * <i>click</i>
0226: * @param result the first position will be fill with the closest coordinate in geometry.
0227: *
0228: * @return the index of the coordinate closest to the click.
0229: */
0230: public int getClosest(Geometry geometry, Coordinate click,
0231: Coordinate[] result) {
0232: Coordinate[] coordinates = geometry.getCoordinates();
0233: int prev = 0;
0234: double mindist = Double.MAX_VALUE;
0235: Coordinate closest = coordinates[coordinates.length - 1];
0236: double x = click.x - closest.x;
0237: double y = click.y - closest.y;
0238: mindist = Math.sqrt(x * x + y * y);
0239: for (int i = 0; i < coordinates.length; i++) {
0240: Coordinate point = coordinates[i];
0241:
0242: x = click.x - point.x;
0243: y = click.y - point.y;
0244: double dist = Math.sqrt(x * x + y * y);
0245: if (dist < mindist) {
0246: mindist = dist;
0247: prev = i;
0248: closest = point;
0249: }
0250: }
0251: result[0] = closest;
0252: if (geometry instanceof LinearRing) {
0253: return (prev == 0 || prev == coordinates.length) ? coordinates.length - 2
0254: : prev;
0255: }
0256: return prev != 0 ? prev - 1 : coordinates.length - 2;
0257: }
0258:
0259: /**
0260: * Returns the closest point on the line between <code>vertex1</code> and <code>vertex2</code> to coordinate
0261: * <code>src</code>
0262: * <p>
0263: * All Coordinates must be in the same CRS
0264: * </p>
0265: *
0266: * @param endPoint1 first vertex of a line.
0267: * @param endPoint2 second vertex of a line.
0268: * @param src the closes coordinate is found with respect to src.
0269: * @return the closest point on the line between <code>vertex1</code> and <code>vertex2</code> to coordinate
0270: * <code>src</code>
0271: */
0272: public Coordinate closestCoordinateOnEdge(Coordinate endPoint1,
0273: Coordinate endPoint2, Coordinate src) {
0274: Coordinate v = new Coordinate();
0275: v.x = endPoint2.x - endPoint1.x;
0276: v.y = endPoint2.y - endPoint1.y;
0277: double d = (v.x * v.x + v.y * v.y);
0278: if (d == 0)
0279: return null;
0280: double t = ((src.x - endPoint1.x) * v.x + (src.y - endPoint1.y)
0281: * v.y)
0282: / d;
0283: if (t < 0 || t > 1 || Double.isInfinite(t) || Double.isNaN(t))
0284: return null;
0285: Coordinate result = new Coordinate();
0286: result.x = endPoint1.x + t * v.x;
0287: result.y = endPoint1.y + t * v.y;
0288: return result;
0289: }
0290:
0291: /**
0292: * Returns the closest point on the line between <code>vertex1</code> and <code>vertex2</code> to coordinate
0293: * <code>src</code>
0294: * <p>
0295: * All Coordinates must be in the same CRS
0296: * </p>
0297: *
0298: * @param endPoint1 first vertex of a line.
0299: * @param endPoint2 second vertex of a line.
0300: * @param src the closes coordinate is found with respect to src.
0301: * @return the closest point on the line between <code>vertex1</code> and <code>vertex2</code> to coordinate
0302: * <code>src</code>
0303: */
0304: public Point closestPointOnEdge(Point endPoint1, Point endPoint2,
0305: Point src) {
0306: if (endPoint1.equals(src))
0307: return src;
0308: if (endPoint2.equals(src))
0309: return src;
0310:
0311: Point v = Point.valueOf(endPoint2.getX() - endPoint1.getX(),
0312: endPoint2.getY() - endPoint1.getY());
0313: int i = v.getX() * v.getX() + v.getY() * v.getY();
0314: if (i == 0)
0315: return null;
0316: int j = (src.getX() - endPoint1.getX()) * v.getX();
0317: int k = (src.getY() - endPoint1.getY()) * v.getY();
0318: double t = (double) (j + k) / (double) i;
0319: if (t >= 1)
0320: return endPoint2;
0321: if (t <= 0)
0322: return endPoint1;
0323:
0324: if (Double.isInfinite(t) || Double.isNaN(t))
0325: return null;
0326:
0327: return Point.valueOf((int) (endPoint1.getX() + t * v.getX()),
0328: (int) (endPoint1.getY() + t * v.getY()));
0329: }
0330:
0331: /**
0332: * Convenience method; transforms the click from the viewportModel CRS to the layer's CRS.
0333: *
0334: * @param click
0335: * @return
0336: */
0337: public Coordinate getTransformedClick(Coordinate click, ILayer layer) {
0338:
0339: try {
0340: MathTransform transform = layer.mapToLayerTransform();
0341: if (transform == null || transform.isIdentity())
0342: return click;
0343: return JTS.transform(click, new Coordinate(), transform);
0344: } catch (Exception e1) {
0345: // CorePlugin.log(ToolsPlugin.getDefault(), e1);
0346: return click;
0347: }
0348: }
0349:
0350: /**
0351: * Finds and returns the EditGeoms that intersect the point. This maybe an expensive operation if there
0352: * are a large number of EditGeoms each with many points. The calculation is done in screen space, however,
0353: * so the number of coordinates in the shapes do not matter so much.
0354: *
0355: * @param editBlackboard
0356: * @param point
0357: * @param treatUnknownAsPolygons
0358: * @return
0359: */
0360: public List<EditGeom> getIntersectingGeom(
0361: EditBlackboard editBlackboard, Point point,
0362: boolean treatUnknownAsPolygons) {
0363: List<EditGeom> geoms = editBlackboard.getGeoms();
0364: List<EditGeom> result = new LinkedList<EditGeom>();
0365: for (EditGeom geom : geoms) {
0366: EditGeomPathIterator iter = EditGeomPathIterator
0367: .getPathIterator(geom);
0368: iter.setPolygon(treatUnknownAsPolygons);
0369: if (iter.toShape().contains(point.getX(), point.getY()))
0370: result.add(geom);
0371: }
0372: return result;
0373: }
0374:
0375: /**
0376: * Returns the coordinate that is on the grid intersection closest to the coordinate.
0377: */
0378: public Coordinate snapToGrid(Point centerPoint, IMap map) {
0379: List<ILayer> layers = map.getMapLayers();
0380:
0381: // by default choose something that will work
0382: ILayer found = layers.get(0);
0383: GridMapGraphic graphic = new GridMapGraphic();
0384: for (ILayer layer : layers) {
0385: if (layer.hasResource(GridMapGraphic.class)) {
0386: found = layer;
0387: try {
0388: graphic = layer.getResource(GridMapGraphic.class,
0389: ProgressManager.instance().get());
0390: } catch (IOException e) {
0391: throw (RuntimeException) new RuntimeException()
0392: .initCause(e);
0393: }
0394: break;
0395: }
0396: }
0397:
0398: double[] closest;
0399: try {
0400: closest = graphic.closest(centerPoint.getX(), centerPoint
0401: .getY(), found);
0402: } catch (FactoryException e) {
0403: EditPlugin.log(null, e);
0404: throw (RuntimeException) new RuntimeException()
0405: .initCause(e);
0406: }
0407: return new Coordinate(closest[0], closest[1], 0);
0408: }
0409:
0410: /**
0411: * Searches all the layers in the map and the EditBlackboard for the closest vertex to center point
0412: * @param includeVerticesInCurrent indicates whether the vertices of the current feature should be considered.
0413: * @param stateAfterSearch
0414: * @return the Point that is the closest vertex.
0415: */
0416: public Coordinate getClosestSnapPoint(EditToolHandler handler,
0417: EditBlackboard editBlackboard, Point centerPoint,
0418: boolean includeVerticesInCurrent,
0419: SnapBehaviour snapBehaviour, EditState stateAfterSearch) {
0420:
0421: IToolContext context = handler.getContext();
0422: MinFinder minFinder = new MinFinder(editBlackboard
0423: .toCoord(centerPoint));
0424: SearchBoxAnimation anim = new SearchBoxAnimation(centerPoint,
0425: new IsBusyStateProvider(handler));
0426:
0427: try {
0428: handler.setCurrentState(EditState.BUSY);
0429: if (snapBehaviour != SnapBehaviour.OFF
0430: && snapBehaviour != SnapBehaviour.GRID)
0431: AnimationUpdater
0432: .runTimer(context.getMapDisplay(), anim);
0433: switch (snapBehaviour) {
0434: case OFF:
0435: return null;
0436: case SELECTED:
0437: searchSelection(handler, editBlackboard, centerPoint,
0438: includeVerticesInCurrent, minFinder);
0439: return minFinder.getMinCoord();
0440: case CURRENT_LAYER:
0441: searchSelection(handler, editBlackboard, centerPoint,
0442: includeVerticesInCurrent, minFinder);
0443: minFinder.add(searchLayer(handler.getEditLayer(),
0444: context, centerPoint));
0445: break;
0446: case ALL_LAYERS:
0447: searchSelection(handler, editBlackboard, centerPoint,
0448: includeVerticesInCurrent, minFinder);
0449: for (ILayer layer : context.getMapLayers()) {
0450: minFinder.add(searchLayer(layer, context,
0451: centerPoint));
0452: }
0453: break;
0454: case GRID:
0455: Coordinate worldCoord = snapToGrid(centerPoint, context
0456: .getMap());
0457: try {
0458: return JTS
0459: .transform(
0460: worldCoord,
0461: null,
0462: editBlackboard.pointCoordCalculator.mapToLayer);
0463: } catch (TransformException e) {
0464: return null;
0465: }
0466: default:
0467: break;
0468: }
0469:
0470: Coordinate min = minFinder.getMinCoord();
0471: try {
0472: if (min == null)
0473: return null;
0474: return JTS.transform(min, new Coordinate(),
0475: editBlackboard.pointCoordCalculator.mapToLayer);
0476: } catch (Exception e) {
0477: EditPlugin.log("", e); //$NON-NLS-1$
0478: return null;
0479: }
0480: } finally {
0481: if (stateAfterSearch == EditState.BUSY)
0482: handler.setCurrentState(EditState.MODIFYING);
0483: else
0484: handler.setCurrentState(stateAfterSearch);
0485: anim.setValid(false);
0486: }
0487:
0488: }
0489:
0490: /**
0491: * Searches the editblackboard and adds the closest vertex to the minFinder
0492: */
0493: private void searchSelection(EditToolHandler handler,
0494: EditBlackboard editBlackboard, Point centerPoint,
0495: boolean includeVerticesInCurrent, MinFinder minFinder) {
0496: Point point = editBlackboard.overVertex(centerPoint,
0497: PreferenceUtil.instance().getSnappingRadius(), true);
0498:
0499: // the vertices in the current geometry should only be considered if inlcudeVerticesInCurrent is true
0500: boolean containsNonCurrentShape = containsNonCurrentShape(
0501: point, editBlackboard, handler.getCurrentShape());
0502: if (point != null
0503: && (includeVerticesInCurrent || containsNonCurrentShape))
0504: minFinder.add(editBlackboard.toCoord(point));
0505: }
0506:
0507: private boolean containsNonCurrentShape(Point p,
0508: EditBlackboard editBlackboard, PrimitiveShape currentShape) {
0509: if (p == null || currentShape == null)
0510: return false;
0511: List<EditGeom> geoms = editBlackboard.getGeoms(p.getX(), p
0512: .getY());
0513: if (geoms.isEmpty())
0514: return false;
0515: if (geoms.size() > 1
0516: || geoms.get(0) != currentShape.getEditGeom())
0517: return true;
0518:
0519: return false;
0520: }
0521:
0522: /**
0523: * Searches the layer for coordinates within snapping distance
0524: *
0525: * @param layer the layer to search.
0526: * @param context the context to use for convenience methods
0527: * @param centerPoint the current centerPoint.
0528: * @return the closest vertex in the layer within the snapping radius or null.
0529: */
0530: private Coordinate searchLayer(ILayer layer, IToolContext context,
0531: Point centerPoint) {
0532: if (!layer.hasResource(FeatureSource.class)
0533: || !layer.isApplicable(EditPlugin.ID)
0534: || !layer.isVisible())
0535: return null;
0536:
0537: ILayer editLayer = context.getEditManager().getEditLayer();
0538: Feature editFeature = context.getEditManager().getEditFeature();
0539: String editFeatureID = null;
0540: if (editFeature != null)
0541: editFeatureID = editFeature.getID();
0542:
0543: Envelope bbox = context.getBoundingBox(new java.awt.Point(
0544: centerPoint.getX(), centerPoint.getY()), PreferenceUtil
0545: .instance().getSnappingRadius() * 2);
0546: try {
0547: Coordinate tmp = context.pixelToWorld(centerPoint.getX(),
0548: centerPoint.getY());
0549: Coordinate layerCenter = JTS.transform(tmp,
0550: new Coordinate(), layer.mapToLayerTransform());
0551: FeatureCollection features = context.getFeaturesInBbox(
0552: layer, bbox);
0553: FeatureIterator iter = null;
0554: try {
0555: Coordinate closest = null;
0556: double minDist = Integer.MAX_VALUE;
0557: for (iter = features.features(); iter.hasNext();) {
0558: Feature feature = iter.next();
0559: if (feature.getID().equals(editFeatureID)
0560: && layer == editLayer)
0561: continue;
0562: Coordinate[] result = new Coordinate[1];
0563: EditUtils.instance.getClosest(feature
0564: .getDefaultGeometry(), layerCenter, result);
0565: double x = layerCenter.x - result[0].x;
0566: double y = layerCenter.y - result[0].y;
0567: double distNew = Math.sqrt(x * x + y * y);
0568:
0569: if (distNew < minDist) {
0570: closest = result[0];
0571: minDist = distNew;
0572: }
0573: }
0574: if (closest != null) {
0575: Coordinate inMapCoords = new Coordinate();
0576: JTS.transform(closest, inMapCoords, layer
0577: .layerToMapTransform());
0578: java.awt.Point point = context
0579: .worldToPixel(inMapCoords);
0580:
0581: double x = centerPoint.getX() - point.x;
0582: double y = centerPoint.getY() - point.y;
0583: double distNew = Math.sqrt(x * x + y * y);
0584: if (distNew < PreferenceUtil.instance()
0585: .getSnappingRadius())
0586: return inMapCoords;
0587: else
0588: return null;
0589: }
0590: } finally {
0591: if (iter != null) {
0592: features.close(iter);
0593: }
0594: }
0595: } catch (Exception e) {
0596: EditPlugin.log("", e); //$NON-NLS-1$
0597: }
0598: return null;
0599: }
0600:
0601: /**
0602: * Keeps track of the point that is the minimum distance to the center point.
0603: * @author Jesse
0604: * @since 1.1.0
0605: */
0606: public static class MinFinder {
0607: private Point centerPoint;
0608: private Point currentMin;
0609: private double distance;
0610: private Coordinate centerCoord;
0611: private Coordinate minCoord;
0612:
0613: public MinFinder(Point centerPoint) {
0614: if (centerPoint == null)
0615: throw new NullPointerException(
0616: "centerPoint cannot be null"); //$NON-NLS-1$
0617: this .centerPoint = centerPoint;
0618: }
0619:
0620: public MinFinder(Coordinate coord) {
0621: this .centerCoord = coord;
0622: }
0623:
0624: public Point getMin() {
0625: return currentMin;
0626: }
0627:
0628: public Coordinate getMinCoord() {
0629: return minCoord;
0630: }
0631:
0632: public void add(Point p) {
0633: if (p == null || p.equals(centerPoint))
0634: return;
0635: if (currentMin == null) {
0636: currentMin = p;
0637: distance = dist(p);
0638: return;
0639: }
0640:
0641: double dist = dist(p);
0642: if (dist < distance) {
0643: currentMin = p;
0644: distance = dist;
0645: }
0646:
0647: }
0648:
0649: public double dist(Point p) {
0650: double x = centerPoint.getX() - p.getX();
0651: double y = centerPoint.getY() - p.getY();
0652: return Math.sqrt(x * x + y * y);
0653: }
0654:
0655: public void add(Coordinate p) {
0656: if (p == null || p.equals(centerCoord))
0657: return;
0658: if (minCoord == null) {
0659: minCoord = p;
0660: distance = dist(p);
0661: return;
0662: }
0663:
0664: double dist = dist(p);
0665: if (dist < distance) {
0666: minCoord = p;
0667: distance = dist;
0668: }
0669:
0670: }
0671:
0672: public double dist(Coordinate p) {
0673: double x = centerCoord.x - p.x;
0674: double y = centerCoord.y - p.y;
0675: return Math.sqrt(x * x + y * y);
0676: }
0677: }
0678:
0679: /**
0680: * Returns the intersection where the two lines meet
0681: */
0682: public Coordinate intersectingLines(Coordinate line1P1,
0683: Coordinate line1P2, Coordinate line2P1, Coordinate line2P2) {
0684:
0685: double B1 = line1P1.x - line1P2.x;
0686: double B2 = line2P1.x - line2P2.x;
0687: double A1 = line1P2.y - line1P1.y;
0688: double A2 = line2P2.y - line2P1.y;
0689: double C1 = A1 * line1P1.x + B1 * line1P1.y;
0690: double C2 = A2 * line2P1.x + B2 * line2P1.y;
0691:
0692: double det = A1 * B2 - A2 * B1;
0693: if (det == 0) {
0694: //Lines are parallel
0695: return null;
0696: }
0697: double x = (B2 * C1 - B1 * C2) / det;
0698: double y = (A1 * C2 - A2 * C1) / det;
0699:
0700: boolean onLine1 = Math.min(line1P1.x, line1P2.x) <= x
0701: && x <= Math.max(line1P1.x, line1P2.x)
0702: && Math.min(line1P1.y, line1P2.y) <= y
0703: && y <= Math.max(line1P1.y, line1P2.y);
0704:
0705: boolean onLine2 = Math.min(line2P1.x, line2P2.x) <= x
0706: && x <= Math.max(line2P1.x, line2P2.x)
0707: && Math.min(line2P1.y, line2P2.y) <= y
0708: && y <= Math.max(line2P1.y, line2P2.y);
0709:
0710: if (onLine1 && onLine2)
0711: return new Coordinate(x, y);
0712:
0713: return null;
0714: }
0715:
0716: /**
0717: * Returns the intersection where the two lines meet
0718: */
0719: public Point intersectingLines(Point line1P1, Point line1P2,
0720: Point line2P1, Point line2P2) {
0721:
0722: int B1 = line1P1.getX() - line1P2.getX();
0723: int B2 = line2P1.getX() - line2P2.getX();
0724: int A1 = line1P2.getY() - line1P1.getY();
0725: int A2 = line2P2.getY() - line2P1.getY();
0726: int C1 = A1 * line1P1.getX() + B1 * line1P1.getY();
0727: int C2 = A2 * line2P1.getX() + B2 * line2P1.getY();
0728:
0729: double det = A1 * B2 - A2 * B1;
0730: if (det == 0) {
0731: //Lines are parallel
0732: return null;
0733: }
0734: double x = (B2 * C1 - B1 * C2) / det;
0735: double y = (A1 * C2 - A2 * C1) / det;
0736:
0737: boolean onLine1 = Math.min(line1P1.getX(), line1P2.getX()) <= x
0738: && x <= Math.max(line1P1.getX(), line1P2.getX())
0739: && Math.min(line1P1.getY(), line1P2.getY()) <= y
0740: && y <= Math.max(line1P1.getY(), line1P2.getY());
0741:
0742: boolean onLine2 = Math.min(line2P1.getX(), line2P2.getX()) <= x
0743: && x <= Math.max(line2P1.getX(), line2P2.getX())
0744: && Math.min(line2P1.getY(), line2P2.getY()) <= y
0745: && y <= Math.max(line2P1.getY(), line2P2.getY());
0746:
0747: if (onLine1 && onLine2)
0748: return Point.valueOf((int) x, (int) y);
0749:
0750: return null;
0751: }
0752:
0753: /**
0754: * Reverse the order of the vertices in a Shape. Used because the holes and shells in polygons have to
0755: * be in a particular order.
0756: *
0757: * @param shape
0758: */
0759: public void reverseOrder(PrimitiveShape shape) {
0760: synchronized (shape.getEditBlackboard()) {
0761: shape.getMutator().reverse();
0762: }
0763: }
0764:
0765: /**
0766: * Appends the points defined in the PathIterator to the shape. Currently curve segments are not supported
0767: * and if there is a moveto in the middle of the iterator a hole will be created in the shape.
0768: * If the GeomType is line or point then an exception will be thrown but otherwise the client code
0769: * must ensure that the request makes sense.
0770: *
0771: * @param iter The iterator to append
0772: * @param shape the shape to append to.
0773: * @return Commands that will append the points to the shape. Nothing is done until commands are run.
0774: */
0775: public UndoableComposite appendPathToShape(EditToolHandler handler,
0776: PathIterator iter, PrimitiveShape shape) {
0777: EditBlackboard bb = shape.getEditBlackboard();
0778: IBlockingProvider<PrimitiveShape> currentProvider = new StaticShapeProvider(
0779: shape);
0780: return appendPathToShape(iter, shape.getEditGeom()
0781: .getShapeType(), handler, bb, currentProvider);
0782: }
0783:
0784: /**
0785: * Appends the points defined in the PathIterator to the shape. Currently curve segments are not supported
0786: * and if there is a move to in the middle of the iterator a hole will be created in the shape.
0787: * If the GeomType is line or point then an exception will be thrown but otherwise the client code
0788: * must ensure that the request makes sense.
0789: *
0790: * @param iter The iterator to append
0791: * @param bb the editblackboard used to add coordinates
0792: * @param currentProvider2 the shape provider that provides the shape to append the coordinates to
0793: * @param shapeType the type of geometry that is expected from currentProvider.
0794: * @return Commands that will append the points to the shape. Nothing is done until commands are run.
0795: */
0796: public UndoableComposite appendPathToShape(PathIterator iter,
0797: ShapeType shapeType, EditToolHandler handler,
0798: EditBlackboard bb,
0799: IBlockingProvider<PrimitiveShape> currentProvider2) {
0800: IBlockingProvider<PrimitiveShape> currentProvider = currentProvider2;
0801:
0802: List<UndoableMapCommand> commands = new ArrayList<UndoableMapCommand>();
0803: commands.add(new StartBatchingCommand(bb));
0804: float[] coords = new float[6];
0805: boolean started = false;
0806: float[] start = new float[2];
0807: AddVertexCommand addVertexCommand = null;
0808: while (!iter.isDone()) {
0809: int type = iter.currentSegment(coords);
0810: switch (type) {
0811: case PathIterator.SEG_MOVETO:
0812: if (!started) {
0813: started = true;
0814: } else {
0815: if (shapeType != ShapeType.POLYGON)
0816: throw new IllegalArgumentException(
0817: "Holes can not to shapes that are not Polygons. Current shape is a " + shapeType); //$NON-NLS-1$
0818: CreateAndSelectHoleCommand command = new CreateAndSelectHoleCommand(
0819: currentProvider);
0820: currentProvider = command.getHoleProvider();
0821: commands.add(command);
0822: }
0823: start[0] = coords[0];
0824: start[1] = coords[1];
0825: // no break is intentional. It has to fall through and add a vertext to the shape
0826: case PathIterator.SEG_LINETO:
0827: addVertexCommand = new AddVertexCommand(handler, bb,
0828: currentProvider, Point.valueOf((int) coords[0],
0829: (int) coords[1]), false);
0830: addVertexCommand.setShowAnimation(false);
0831: commands.add(addVertexCommand);
0832: break;
0833: case PathIterator.SEG_CLOSE:
0834: if (!Point.valueOf((int) coords[0], (int) coords[1])
0835: .equals(
0836: Point.valueOf((int) start[0],
0837: (int) start[1]))) {
0838: addVertexCommand = new AddVertexCommand(handler,
0839: bb, currentProvider, Point.valueOf(
0840: (int) start[0], (int) start[1]),
0841: false);
0842: addVertexCommand.setShowAnimation(false);
0843: commands.add(addVertexCommand);
0844: }
0845: break;
0846: default:
0847: throw new UnsupportedOperationException("not supported"); //$NON-NLS-1$
0848:
0849: }
0850: iter.next();
0851: }
0852:
0853: if (shapeType == ShapeType.POLYGON
0854: && addVertexCommand != null
0855: && !addVertexCommand.getPointToAdd().equals(
0856: Point.valueOf((int) start[0], (int) start[1]))) {
0857: commands.add(new AddVertexCommand(handler, bb,
0858: currentProvider, Point.valueOf((int) start[0],
0859: (int) start[1]), false));
0860: }
0861:
0862: UndoableComposite undoableComposite = new UndoableComposite(
0863: commands);
0864: undoableComposite.getFinalizerCommands().add(
0865: new FireEventsCommand(bb));
0866: return undoableComposite;
0867: }
0868:
0869: private static class StartBatchingCommand extends AbstractCommand
0870: implements UndoableMapCommand {
0871: private EditBlackboard bb;
0872:
0873: StartBatchingCommand(EditBlackboard bb) {
0874: this .bb = bb;
0875: }
0876:
0877: public void run(IProgressMonitor monitor) throws Exception {
0878: bb.startBatchingEvents();
0879: }
0880:
0881: public String getName() {
0882: return null;
0883: }
0884:
0885: public void rollback(IProgressMonitor monitor) throws Exception {
0886: }
0887:
0888: }
0889:
0890: private static class FireEventsCommand extends AbstractCommand
0891: implements UndoableMapCommand {
0892: private EditBlackboard bb;
0893:
0894: FireEventsCommand(EditBlackboard bb) {
0895: this .bb = bb;
0896: }
0897:
0898: public void run(IProgressMonitor monitor) throws Exception {
0899: bb.fireBatchedEvents();
0900: }
0901:
0902: public String getName() {
0903: return null;
0904: }
0905:
0906: public void rollback(IProgressMonitor monitor) throws Exception {
0907: }
0908:
0909: }
0910:
0911: public static class StaticShapeProvider implements
0912: IBlockingProvider<PrimitiveShape> {
0913: private PrimitiveShape shape;
0914:
0915: public StaticShapeProvider(PrimitiveShape shape) {
0916: this .shape = shape;
0917: }
0918:
0919: public PrimitiveShape get(IProgressMonitor monitor) {
0920: return shape;
0921: }
0922:
0923: }
0924:
0925: public static class EditToolHandlerShapeProvider implements
0926: IBlockingProvider<PrimitiveShape> {
0927:
0928: private EditToolHandler handler;
0929:
0930: public EditToolHandlerShapeProvider(EditToolHandler handler) {
0931: this .handler = handler;
0932: }
0933:
0934: public PrimitiveShape get(IProgressMonitor monitor) {
0935: return handler.getCurrentShape();
0936: }
0937:
0938: }
0939:
0940: /**
0941: * Provider for EditGeoms
0942: *
0943: * @author jones
0944: * @since 1.1.0
0945: */
0946: public static class StaticEditGeomProvider implements
0947: IBlockingProvider<EditGeom> {
0948:
0949: private EditGeom geom;
0950:
0951: public StaticEditGeomProvider(EditGeom geom) {
0952: this .geom = geom;
0953: }
0954:
0955: public EditGeom get(IProgressMonitor monitor) {
0956: return geom;
0957: }
0958:
0959: }
0960:
0961: public static Coordinate midPointOnLine(Coordinate coord,
0962: Coordinate coord2) {
0963: double x = (coord.x + coord2.x) / 2;
0964: double y = (coord.y + coord2.y) / 2;
0965: return new Coordinate(x, y);
0966: }
0967:
0968: /**
0969: * The framework stores the current shape and state on a layer when the currently selected layer changes. This
0970: * method clears that cache on the layers passed in. This should be called when the tool is de-actived and when
0971: * a cancel and accept is run. This is so that the shapes can be garbage collected.
0972: *
0973: * @param layers
0974: */
0975: public void clearLayerStateShapeCache(Collection<ILayer> layers) {
0976: for (ILayer layer : layers) {
0977: layer.getBlackboard().put(
0978: EditToolHandler.STORED_CURRENT_SHAPE, null);
0979: layer.getBlackboard().put(
0980: EditToolHandler.STORED_CURRENT_STATE, null);
0981: }
0982: }
0983:
0984: /**
0985: * When an edit is canceled the selected layer must be re-rendered because they were hidden by {@link #refreshLayer(ILayer, Feature, Envelope, boolean, boolean)}
0986: * This method must be called in order to efficiently do that.
0987: *
0988: * @see #refreshLayer(ILayer, Feature, Envelope, boolean, boolean)
0989: *
0990: * @param selectedLayer
0991: */
0992: public void cancelHideSelection(ILayer selectedLayer) {
0993: if (selectedLayer == null)
0994: return;
0995:
0996: IBlackboard properties = selectedLayer.getBlackboard();
0997: if (!PreferenceUtil.instance().hideSelectedLayers()) {
0998: properties.put(
0999: ProjectBlackboardConstants.MAP__RENDERING_FILTER,
1000: null);
1001: ((ViewportPane) selectedLayer.getMap().getRenderManager()
1002: .getMapDisplay()).repaint();
1003: return;
1004: }
1005:
1006: Filter filter = (Filter) properties
1007: .get(ProjectBlackboardConstants.MAP__RENDERING_FILTER);
1008: if (filter == null)
1009: return;
1010: properties.put(
1011: ProjectBlackboardConstants.MAP__RENDERING_FILTER, null);
1012:
1013: Envelope env = (Envelope) properties.get(EDIT_FEATURE_BOUNDS);
1014: properties.put(EDIT_FEATURE_BOUNDS, null);
1015: selectedLayer.refresh(env);
1016: }
1017:
1018: /**
1019: * Triggers a re-render that hides the features on the {@link EditBlackboard}.
1020: */
1021: public void hideSelectedFeatures(EditToolHandler handler,
1022: ILayer selectedLayer) {
1023: Envelope env = new Envelope();
1024:
1025: Set<String> fids = new HashSet<String>();
1026: for (EditGeom geom : handler.getEditBlackboard(selectedLayer)
1027: .getGeoms()) {
1028: if (env.isNull()) {
1029: env.init(geom.getShell().getEnvelope());
1030: } else {
1031: env.expandToInclude(geom.getShell().getEnvelope());
1032: }
1033: fids.add(geom.getFeatureIDRef().get());
1034: }
1035: EditUtils.instance.refreshLayer(selectedLayer, fids, env, true,
1036: true);
1037: }
1038:
1039: /**
1040: * Sets the rendering hint on the layer so that the feature is hidden if hidefeature is true. If hidefeature is false then
1041: * the hint is reset all features are shown
1042: *
1043: * <p>
1044: * {@link #cancelHideSelection(ILayer)} should be called if the edit is canceled.
1045: * </p>
1046: * @see #cancelHideSelection(ILayer)
1047: *
1048: * @param selectedLayer
1049: * @param feature
1050: * @param refreshBounds the area to refresh (should be the the area of the feature). May be null to refresh entire area. Envelope should be in
1051: * Layer coordinates.
1052: * @param hidefeature
1053: */
1054: public void refreshLayer(ILayer selectedLayer, Feature feature,
1055: Envelope refreshBounds, boolean forceRefresh,
1056: boolean hidefeature) {
1057: Set<String> fids = Collections.singleton(feature.getID());
1058: refreshLayer(selectedLayer, fids, refreshBounds, forceRefresh,
1059: hidefeature);
1060: }
1061:
1062: /**
1063: * Sets the rendering hint on the layer so that the feature is hidden if hidefeature is true. If hidefeature is false then
1064: * the hint is reset all features are shown
1065: *
1066: * <p>
1067: * {@link #cancelHideSelection(ILayer)} should be called if the edit is cancelled.
1068: * </p>
1069: * @see #cancelHideSelection(ILayer)
1070: *
1071: * @param selectedLayer the currently selected layer
1072: * @param fids the Feature Ids of the features that have been selected
1073: * @param refreshBounds the area to refresh (should be the the area of the features). May be null to refresh entire area. Envelope should be in
1074: * Layer coordinates.
1075: * @param hidefeature if true then the features are hidden otherwise they will be shown again.
1076: */
1077: public void refreshLayer(ILayer selectedLayer, Set<String> fids,
1078: Envelope refreshBounds, boolean forceRefresh,
1079: boolean hidefeature) {
1080: if (selectedLayer == null)
1081: return;
1082: IBlackboard properties = selectedLayer.getBlackboard();
1083: if (!PreferenceUtil.instance().hideSelectedLayers()) {
1084: properties.put(
1085: ProjectBlackboardConstants.MAP__RENDERING_FILTER,
1086: null);
1087: ((ViewportPane) selectedLayer.getMap().getRenderManager()
1088: .getMapDisplay()).repaint();
1089: return;
1090: }
1091:
1092: if (!forceRefresh && fids.isEmpty())
1093: return;
1094:
1095: FilterFactory filterFactory = FilterFactoryFinder
1096: .createFilterFactory();
1097: boolean modified = false;
1098: for (String fid : fids) {
1099: if (fid == null)
1100: continue;
1101: if (hidefeature) {
1102: setAffectedArea(refreshBounds, properties);
1103:
1104: modified = addFidToExcludeFilter(properties, fid,
1105: filterFactory)
1106: || modified;
1107: } else {
1108: //get area to refresh and refresh it
1109: modified = removeFidFromExcludeFilter(properties, fid,
1110: filterFactory)
1111: || modified;
1112: }
1113: }
1114:
1115: if ((refreshBounds != null || forceRefresh) && modified)
1116: selectedLayer.refresh(refreshBounds);
1117: }
1118:
1119: private boolean removeFidFromExcludeFilter(IBlackboard properties,
1120: String fid, FilterFactory filterFactory) {
1121: Filter filter = (Filter) properties
1122: .get(ProjectBlackboardConstants.MAP__RENDERING_FILTER);
1123: Filter f = Filter.ALL;
1124: boolean modified = false;
1125: if (filter instanceof FidFilter) {
1126: FidFilter fidFilter = (FidFilter) filter;
1127: fidFilter.removeFid(fid);
1128: f = fidFilter;
1129: if (fidFilter.getFids().length == 0)
1130: f = Filter.ALL;
1131:
1132: modified = true;
1133: } else {
1134: if (filter != null) {
1135: f = filterFactory.createFidFilter(fid).not();
1136: try {
1137: f = filterFactory.createLogicFilter(f, filter,
1138: FilterType.LOGIC_OR);
1139: } catch (IllegalFilterException e) {
1140: throw (RuntimeException) new RuntimeException()
1141: .initCause(e);
1142: }
1143: modified = true;
1144: }
1145:
1146: }
1147: if (f == Filter.ALL)
1148: f = null;
1149: properties.put(
1150: ProjectBlackboardConstants.MAP__RENDERING_FILTER, f);
1151:
1152: return modified;
1153: }
1154:
1155: private boolean addFidToExcludeFilter(IBlackboard properties,
1156: String fid, FilterFactory filterFactory) {
1157: //get area to refresh and refresh it
1158: Filter filter = (Filter) properties
1159: .get(ProjectBlackboardConstants.MAP__RENDERING_FILTER);
1160:
1161: Filter f;
1162: boolean modified = false;
1163: if (filter instanceof FidFilter) {
1164: FidFilter fidFilter = (FidFilter) filter;
1165: fidFilter.addFid(fid);
1166: f = fidFilter;
1167: modified = true;
1168: } else {
1169: f = filterFactory.createFidFilter(fid);
1170:
1171: if (filter != null) {
1172: try {
1173: f = filterFactory.createLogicFilter(f, filter,
1174: FilterType.LOGIC_OR);
1175: } catch (IllegalFilterException e) {
1176: throw (RuntimeException) new RuntimeException()
1177: .initCause(e);
1178: }
1179: }
1180: modified = true;
1181: }
1182: properties.put(
1183: ProjectBlackboardConstants.MAP__RENDERING_FILTER, f);
1184:
1185: return modified;
1186: }
1187:
1188: /**
1189: * caches the bounds of all the refresh areas for {@link #cancelHideSelection(ILayer)} so it knows what area to refresh
1190: *
1191: * @param refreshBounds
1192: * @param properties
1193: */
1194: private void setAffectedArea(Envelope refreshBounds,
1195: IBlackboard properties) {
1196: Envelope bounds = (Envelope) properties
1197: .get(EDIT_FEATURE_BOUNDS);
1198: if (refreshBounds == null) {
1199: bounds = null;
1200: } else {
1201: if (bounds == null)
1202: bounds = new Envelope(refreshBounds);
1203: else {
1204: bounds.expandToInclude(refreshBounds);
1205: }
1206: }
1207:
1208: properties.put(EDIT_FEATURE_BOUNDS, bounds);
1209: }
1210:
1211: /**
1212: * Returns the Geometry from the collection that the mouse is over/intersects
1213: *
1214: * @param geoms Geoms to search through
1215: * @param location the location
1216: * @return the first geom that the location is over/intersects
1217: */
1218: public EditGeom getGeomWithMouseOver(Collection<EditGeom> geoms,
1219: Point location, boolean treatUnknownAsPolygon) {
1220: EditGeom over = geoms.iterator().next();
1221: for (EditGeom geom : geoms) {
1222: PrimitiveShapeIterator iter = PrimitiveShapeIterator
1223: .getPathIterator(geom.getShell());
1224: if (iter.toShape().contains(location.getX(),
1225: location.getY())) {
1226: over = geom;
1227: break;
1228: }
1229: ClosestEdge edge = geom.getShell().getClosestEdge(location,
1230: treatUnknownAsPolygon);
1231: if (edge != null
1232: && edge.getDistanceToEdge() <= PreferenceUtil
1233: .instance().getVertexRadius()) {
1234: over = geom;
1235: break;
1236: }
1237: }
1238: return over;
1239: }
1240:
1241: /**
1242: * Returns true if the shape has a self intersection.
1243: * Only checks the points not the coordinates there for it is quicker but less accurate.
1244: *
1245: * @param shape shape to test.
1246: * @return true if the shape has a self intersection.
1247: */
1248:
1249: public boolean selfIntersection(PrimitiveShape shape) {
1250: if (shape.getNumPoints() < 3)
1251: return false;
1252: for (int i = 1; i < shape.getNumPoints(); i++) {
1253: Point last = shape.getPoint(i - 1);
1254: Point current = shape.getPoint(i);
1255: if (intersection(last, current, shape, i, shape
1256: .getNumPoints() - 1, false))
1257: return true;
1258: }
1259:
1260: return false;
1261: }
1262:
1263: /**
1264: * Checks whether the edge from point1 to point2 intersects any edge in the shape from startIndex to the endIndex
1265: *
1266: * @param point1 the first point in the reference edge
1267: * @param point2 the second point in the reference edge
1268: * @param shape the shape that is searched for intersections
1269: * @param startIndex the index in the shape of the point at which to start searching. The point indicated will be the
1270: * first point in the edge.
1271: * @param endIndex the index to stop the search. It is the index of the end point of the last edge to compare
1272: * @return true if there is an intersection between the edge indicated by last,current and the shape
1273: */
1274: public boolean intersection(Point point1, Point point2,
1275: PrimitiveShape shape, int startIndex, int endIndex) {
1276: return intersection(point1, point2, shape, startIndex,
1277: endIndex, true);
1278: }
1279:
1280: /**
1281: * Checks whether the edge from point1 to point2 intersects any edge in the shape from startIndex to the endIndex
1282: *
1283: * @param point1 the first point in the reference edge
1284: * @param point2 the second point in the reference edge
1285: * @param shape the shape that is searched for intersections
1286: * @param startIndex the index in the shape of the point at which to start searching. The point indicated will be the
1287: * first point in the edge.
1288: * @param endIndex the index to stop the search. It is the index of the end point of the last edge to compare
1289: * @param referenceLineIntersections if true then if a line crosses one of the endpoints of the reference line
1290: * it is not considered a intersection since it is the connecting point to the rest of the shape.
1291: * @return true if there is an intersection between the edge indicated by last,current and the shape
1292: */
1293: private boolean intersection(Point point1, Point point2,
1294: PrimitiveShape shape, int startIndex, int endIndex,
1295: boolean referenceLineIntersections) {
1296:
1297: for (int j = startIndex + 1; j < endIndex + 1; j++) {
1298: Point last2 = shape.getPoint(j - 1);
1299: Point current2 = shape.getPoint(j);
1300: if (last2.equals(point1))
1301: continue; // same edge so continue.
1302:
1303: if (linesParallel(point1, point2, last2, current2)) {
1304: if (sameDirection(point1, point2, last2, current2)
1305: && last2.equals(point2)) {
1306: return true;
1307: } else {
1308: // no intersection
1309: continue;
1310: }
1311: }
1312:
1313: Point intersectingLines = intersectingLines(point1, point2,
1314: last2, current2);
1315: if (intersectingLines != null) {
1316: Point endPoint2;
1317: Point endPoint1;
1318: if (referenceLineIntersections) {
1319: endPoint1 = point1;
1320: endPoint2 = point2;
1321: } else {
1322: endPoint1 = last2;
1323: endPoint2 = current2;
1324: }
1325: if (!intersectingLines.equals(endPoint1)
1326: && !intersectingLines.equals(endPoint2))
1327: return true;
1328: }
1329: }
1330:
1331: return false;
1332: }
1333:
1334: private boolean sameDirection(Point last, Point current,
1335: Point last2, Point current2) {
1336: int dx1 = last.getX() - current.getX();
1337: int dy1 = last.getY() - current.getY();
1338: int dy2 = last2.getY() - current2.getY();
1339: int dx2 = last2.getX() - current2.getX();
1340: double length1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
1341: double length2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
1342:
1343: if (dx1 / length1 == dx2 / length2
1344: && dy1 / length1 == dy2 / length2) {
1345: return false;
1346: }
1347: return true;
1348: }
1349:
1350: private boolean linesParallel(Point line1P1, Point line1P2,
1351: Point line2P1, Point line2P2) {
1352: int B1 = line1P1.getX() - line1P2.getX();
1353: int B2 = line2P1.getX() - line2P2.getX();
1354: int A1 = line1P2.getY() - line1P1.getY();
1355: int A2 = line2P2.getY() - line2P1.getY();
1356:
1357: double det = A1 * B2 - A2 * B1;
1358: if (det == 0) {
1359: //Lines are parallel
1360: return true;
1361: }
1362: return false;
1363: }
1364: }
|