/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ /* * SimpleMoveListener.java * * Created on 10. M\u00E4rz 2005, 10:10 */ package de.cismet.cismap.commons.gui.piccolo.eventlistener; import com.vividsolutions.jts.geom.*; import edu.umd.cs.piccolo.event.PBasicInputEventHandler; import edu.umd.cs.piccolo.event.PInputEvent; import edu.umd.cs.piccolo.nodes.PPath; import edu.umd.cs.piccolo.util.PBounds; import edu.umd.cs.piccolox.util.PLocator; import java.awt.Color; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.swing.SwingWorker; import de.cismet.cismap.commons.features.DefaultFeatureCollection; import de.cismet.cismap.commons.features.Feature; import de.cismet.cismap.commons.gui.MappingComponent; import de.cismet.cismap.commons.gui.piccolo.PFeature; import de.cismet.cismap.commons.gui.piccolo.PHandle; import de.cismet.cismap.commons.tools.PFeatureTools; /** * DOCUMENT ME! * * @author hell/nh * @version $Revision$, $Date$ */ public class PerpendicularIntersectionListener extends PBasicInputEventHandler { //~ Static fields/initializers --------------------------------------------- private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger( PerpendicularIntersectionListener.class); private static final Color COLOR_PERPENDICULAR_HANDLE = new Color(205, 133, 0, 150); //~ Enums ------------------------------------------------------------------ /** * DOCUMENT ME! * * @version $Revision$, $Date$ */ private static enum Stage { //~ Enum constants ----------------------------------------------------- SELECT_FEATURE, CREATE_PERPENDICULAR, ADD_HANDLES } //~ Instance fields -------------------------------------------------------- private final MappingComponent mappingComponent; private LineSegment perpendicularSegment = null; private Coordinate perpendicularCoordinate; private PPath perpendicularPathLine = null; private PHandle perpendicularHandle = null; private PLocator locator = null; private Feature selectedFeature = null; private Collection<AddHandle> addHandles = new ArrayList<AddHandle>(); private Collection<PHandle> featureHandles = new ArrayList<PHandle>(); private Stage stage = Stage.SELECT_FEATURE; //~ Constructors ----------------------------------------------------------- /** * Creates a new PerpendicularIntersectionListener object. * * @param mappingComponent DOCUMENT ME! */ public PerpendicularIntersectionListener(final MappingComponent mappingComponent) { super(); this.mappingComponent = mappingComponent; perpendicularPathLine = new PPath(); locator = new PLocator() { @Override public double locateX() { if (perpendicularCoordinate != null) { return perpendicularCoordinate.x; } else { return Double.MIN_VALUE; } } @Override public double locateY() { if (perpendicularCoordinate != null) { return perpendicularCoordinate.y; } else { return Double.MIN_VALUE; } } }; perpendicularHandle = new PHandle(locator, mappingComponent) { @Override public void handleClicked(final PInputEvent e) { finishCreatePerpendicular(); } }; perpendicularHandle.setPaint(COLOR_PERPENDICULAR_HANDLE); } //~ Methods ---------------------------------------------------------------- /** * DOCUMENT ME! * * @param feature DOCUMENT ME! */ private void createFeatureHandles(final Feature feature) { createFeatureHandles(feature, stage == Stage.ADD_HANDLES); } /** * DOCUMENT ME! * * @param feature DOCUMENT ME! * @param isInAddHandleStage DOCUMENT ME! */ private void createFeatureHandles(final Feature feature, final boolean isInAddHandleStage) { createFeatureHandles(Arrays.asList(new Feature[] { feature }), isInAddHandleStage); } /** * DOCUMENT ME! * * @param col DOCUMENT ME! * @param isInAddHandleStage DOCUMENT ME! */ private synchronized void createFeatureHandles(final Collection col, final boolean isInAddHandleStage) { if (isInAddHandleStage) { addHandles.clear(); } featureHandles.clear(); for (final Object obj : col) { if ((obj instanceof Feature) && ((Feature)obj).isEditable()) { final Feature feature = (Feature)obj; final PFeature pFeature = (PFeature)mappingComponent.getPFeatureHM().get(feature); if (pFeature != null) { final Geometry geometry = pFeature.getFeature().getGeometry(); if ((geometry instanceof Polygon) || (geometry instanceof LineString) || (geometry instanceof MultiPolygon)) { for (int entityIndex = 0; entityIndex < pFeature.getNumOfEntities(); entityIndex++) { for (int ringIndex = 0; ringIndex < pFeature.getNumOfRings(entityIndex); ringIndex++) { final float[] xp = pFeature.getXp(entityIndex, ringIndex); final float[] yp = pFeature.getYp(entityIndex, ringIndex); // for (int coordPosition = 0; coordPosition < (xp.length - 1); coordPosition++) { // WARNING ! iterating backwards for important reason !!! // If we iterate forwards, the insertion of multiple points // of the same ring will fail later ! // This is because after the insertion of the first coordinate // all following insertion have to be shift to one later coordinate. // So it is far easier to iterate backwards to avoid this. for (int coordPosition = xp.length - 1; coordPosition > 0; coordPosition--) { final LineSegment segment = new LineSegment( xp[coordPosition - 1], yp[coordPosition - 1], xp[coordPosition], yp[coordPosition]); featureHandles.add(new PHandle(new PLocator() { @Override public double locateX() { return segment.p0.x; } @Override public double locateY() { return segment.p0.y; } }, mappingComponent)); if (isInAddHandleStage) { final Coordinate intersectionCoord = segment.intersection(perpendicularSegment); if (intersectionCoord != null) { // pFeature arbeitet nur mit float genauigkeit final Coordinate floatSegmentStartCoord = new Coordinate((float) segment.p0.x, (float)segment.p0.y); final Coordinate floatSegmentEndCoord = new Coordinate((float)segment.p1.x, (float)segment.p1.y); if (!(floatSegmentStartCoord.equals2D(intersectionCoord) || floatSegmentEndCoord.equals2D(intersectionCoord))) { final AddHandle addHandle = new AddHandle( pFeature, entityIndex, ringIndex, coordPosition, intersectionCoord, segment); addHandles.add(addHandle); } } } } } } } } } } } /** * DOCUMENT ME! */ private void createAddHandles() { createFeatureHandles(mappingComponent.getFeatureCollection().getAllFeatures(), true); } /** * DOCUMENT ME! * * @param event DOCUMENT ME! */ private synchronized void recalculatePerpendicular(final PInputEvent event) { perpendicularSegment = null; perpendicularCoordinate = null; if (selectedFeature != null) { final Point2D triggerPoint = event.getPosition(); final Coordinate triggerCoord = new Coordinate(triggerPoint.getX(), triggerPoint.getY()); final LineSegment nearestSegment = getNearestSegment(triggerCoord, selectedFeature); if (nearestSegment != null) { final int snappingDistance = mappingComponent.getSnappingRectSize() / 2; if (mappingComponent.isSnappingEnabled()) { final Point2D localTriggerPoint = mappingComponent.getCamera() .viewToLocal((Point2D)triggerPoint.clone()); final Point2D localNearestSegmentStartPoint = mappingComponent.getCamera() .viewToLocal(new Point2D.Double(nearestSegment.p0.x, nearestSegment.p0.y)); final Point2D localNearestSegmentEndPoint = mappingComponent.getCamera() .viewToLocal(new Point2D.Double(nearestSegment.p1.x, nearestSegment.p1.y)); if (localTriggerPoint.distance(localNearestSegmentStartPoint) < snappingDistance) { perpendicularCoordinate = nearestSegment.p0; } else if (localTriggerPoint.distance(localNearestSegmentEndPoint) < snappingDistance) { perpendicularCoordinate = nearestSegment.p1; } } if (perpendicularCoordinate == null) { perpendicularCoordinate = nearestSegment.closestPoint(triggerCoord); } // minimum bounds is viewbounds final PBounds bounds = new PBounds(mappingComponent.getCamera().getViewBounds()); // extend bounds to bounds of all features in featurecollection final List<Feature> allFeatures = mappingComponent.getFeatureCollection().getAllFeatures(); for (final Feature feature : allFeatures) { final PFeature pfeature = (PFeature)mappingComponent.getPFeatureHM().get(feature); bounds.add(pfeature.getBounds()); } // create all 4 bounds lines final LineSegment top = new LineSegment( bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMinY()); final LineSegment bottom = new LineSegment( bounds.getMinX(), bounds.getMaxY(), bounds.getMaxX(), bounds.getMaxY()); final LineSegment left = new LineSegment( bounds.getMinX(), bounds.getMinY(), bounds.getMinX(), bounds.getMaxY()); final LineSegment right = new LineSegment( bounds.getMaxX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY()); // the diagonal of the bounds is the longest possible line // that can cross the boundslines final double maxLength = Math.sqrt( Math.pow(bounds.getWidth(), 2) * Math.pow(bounds.getHeight(), 2)); // create left and right perpendicular of the segment final LineSegment leftyPerpendicular = leftyPerpendicular( nearestSegment, perpendicularCoordinate, maxLength); final LineSegment rightyPerpendicular = rightyPerpendicular( nearestSegment, perpendicularCoordinate, maxLength); // check for each bounds line if it intersects with the perpendiculars final Collection<LineSegment> boundLines = new ArrayList<LineSegment>(); boundLines.add(top); boundLines.add(bottom); boundLines.add(left); boundLines.add(right); Coordinate leftyIntersection = null; Coordinate rightyIntersection = null; final Iterator<LineSegment> boundsIterator = boundLines.iterator(); while (boundsIterator.hasNext() && ((leftyIntersection == null) || (rightyIntersection == null))) { final LineSegment boundsLine = boundsIterator.next(); if (leftyIntersection == null) { leftyIntersection = boundsLine.intersection(leftyPerpendicular); } if (rightyIntersection == null) { rightyIntersection = boundsLine.intersection(rightyPerpendicular); } } // if both intersection points were found (should be the case everytime) // then the perpendicular line is determined by the 2 points if ((leftyIntersection != null) && (rightyIntersection != null)) { perpendicularSegment = new LineSegment(leftyIntersection, rightyIntersection); } } } } @Override public void mouseMoved(final PInputEvent event) { super.mouseMoved(event); try { if (stage == Stage.CREATE_PERPENDICULAR) { new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { recalculatePerpendicular(event); return null; } @Override protected void done() { try { get(); } catch (final Exception ex) { LOG.warn(ex, ex); } refreshHandles(); relocatePerpendicularHandle(); } }.execute(); } } catch (final Exception ex) { LOG.info("Exception in mouseMoved", ex); } } /** * DOCUMENT ME! */ private void relocatePerpendicularHandle() { if (mappingComponent.getHandleLayer().getChildrenReference().contains(perpendicularPathLine)) { if (perpendicularSegment != null) { final Point2D localPerpendicularSegmentStartPoint = mappingComponent.getCamera() .viewToLocal(new Point2D.Double(perpendicularSegment.p0.x, perpendicularSegment.p0.y)); final Point2D localPerpendicularSegmentEndPoint = mappingComponent.getCamera() .viewToLocal(new Point2D.Double(perpendicularSegment.p1.x, perpendicularSegment.p1.y)); perpendicularPathLine.setPathToPolyline( new Point2D[] { localPerpendicularSegmentStartPoint, localPerpendicularSegmentEndPoint }); } } if (mappingComponent.getHandleLayer().getChildrenReference().contains(perpendicularHandle)) { if (perpendicularCoordinate != null) { perpendicularHandle.relocateHandle(); } } } /** * DOCUMENT ME! * * @param visible DOCUMENT ME! * @param pPaths DOCUMENT ME! */ private void setPPathVisible(final boolean visible, final PPath... pPaths) { for (final PPath pPath : pPaths) { final boolean found = mappingComponent.getHandleLayer().getChildrenReference().contains(pPath); if (visible) { if (!found) { mappingComponent.getHandleLayer().addChild(pPath); } } else { if (found) { mappingComponent.getHandleLayer().removeChild(pPath); } } } } /** * DOCUMENT ME! * * @param visible DOCUMENT ME! */ private void setPerpendicularHandleVisible(final boolean visible) { setPPathVisible(visible, perpendicularHandle); } /** * DOCUMENT ME! * * @param visible DOCUMENT ME! */ private void setFeatureHandlesVisible(final boolean visible) { setPPathVisible(visible, featureHandles.toArray(new PPath[0])); } /** * DOCUMENT ME! * * @param visible DOCUMENT ME! */ private void setAddHandlesVisible(final boolean visible) { setPPathVisible(visible, addHandles.toArray(new PPath[0])); } /** * DOCUMENT ME! * * @param visible DOCUMENT ME! */ private void setPerpendicularLineVisible(final boolean visible) { setPPathVisible(visible, perpendicularPathLine); } /** * DOCUMENT ME! */ public void init() { if ((mappingComponent.getFeatureCollection() instanceof DefaultFeatureCollection) && (((DefaultFeatureCollection)mappingComponent.getFeatureCollection()).getSelectedFeatures() .size() == 1)) { final Feature old = selectedFeature; selectedFeature = ((Collection<Feature>)((DefaultFeatureCollection)mappingComponent.getFeatureCollection()) .getSelectedFeatures()).toArray(new Feature[0])[0]; if (old != selectedFeature) { perpendicularSegment = null; perpendicularCoordinate = null; stage = Stage.CREATE_PERPENDICULAR; } } else { perpendicularSegment = null; perpendicularCoordinate = null; selectedFeature = null; stage = Stage.SELECT_FEATURE; } new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { if (selectedFeature == null) { featureHandles.clear(); addHandles.clear(); } else { createFeatureHandles(selectedFeature); } return null; } @Override protected void done() { try { get(); } catch (final Exception ex) { LOG.warn(ex, ex); } refreshHandles(); } }.execute(); } /** * DOCUMENT ME! */ private void refreshHandles() { mappingComponent.getHandleLayer().removeAllChildren(); setPerpendicularLineVisible((perpendicularSegment != null) && (stage != Stage.SELECT_FEATURE)); setFeatureHandlesVisible(stage != Stage.SELECT_FEATURE); setAddHandlesVisible(stage == Stage.ADD_HANDLES); setPerpendicularHandleVisible((perpendicularCoordinate != null) && (stage == Stage.CREATE_PERPENDICULAR)); } @Override public void mouseClicked(final PInputEvent event) { super.mouseClicked(event); if (event.isRightMouseButton()) { mappingComponent.getFeatureCollection().unselectAll(); stage = Stage.SELECT_FEATURE; refreshHandles(); } else if (event.isLeftMouseButton()) { if (stage == Stage.SELECT_FEATURE) { final PFeature pFeature = (PFeature)PFeatureTools.getFirstValidObjectUnderPointer( event, new Class[] { PFeature.class }); if (pFeature != null) { selectedFeature = pFeature.getFeature(); mappingComponent.getFeatureCollection().select(selectedFeature); new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { stage = Stage.CREATE_PERPENDICULAR; recalculatePerpendicular(event); createFeatureHandles(selectedFeature); return null; } @Override protected void done() { try { get(); } catch (final Exception ex) { LOG.warn(ex, ex); } refreshHandles(); relocatePerpendicularHandle(); } }.execute(); } } else if (stage == Stage.CREATE_PERPENDICULAR) { finishCreatePerpendicular(); } else if (stage == Stage.ADD_HANDLES) { if (event.getClickCount() == 2) { finishAddHandles(); } } } } /** * DOCUMENT ME! */ private void finishCreatePerpendicular() { new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { stage = Stage.ADD_HANDLES; createAddHandles(); return null; } @Override protected void done() { try { get(); } catch (final Exception ex) { LOG.warn(ex, ex); } refreshHandles(); } }.execute(); } /** * DOCUMENT ME! */ private void finishAddHandles() { new SwingWorker<Boolean, Void>() { @Override protected Boolean doInBackground() throws Exception { stage = Stage.CREATE_PERPENDICULAR; for (final AddHandle addHandle : addHandles) { if (addHandle.isSelected()) { addHandle.insertCoordinate(); } } createFeatureHandles(selectedFeature); return null; } @Override protected void done() { try { get(); } catch (final Exception ex) { LOG.warn(ex, ex); } refreshHandles(); } }.execute(); } /** * DOCUMENT ME! * * @param segment DOCUMENT ME! * @param perpendicularStart DOCUMENT ME! * @param length DOCUMENT ME! * @param isLefty DOCUMENT ME! * * @return DOCUMENT ME! */ private LineSegment perpendicular(final LineSegment segment, final Coordinate perpendicularStart, final double length, final boolean isLefty) { final double deltaX = segment.p1.x - segment.p0.x; final double deltaY = segment.p1.y - segment.p0.y; final double alpha = Math.atan2(deltaY, deltaX); final double alpha90 = alpha + Math.toRadians((isLefty) ? -90 : 90); final double x = Math.cos(alpha90) * length; final double y = Math.sin(alpha90) * length; final Coordinate perpendicularEnd = new Coordinate(x + perpendicularStart.x, y + perpendicularStart.y); return new LineSegment(perpendicularStart, perpendicularEnd); } /** * DOCUMENT ME! * * @param segment DOCUMENT ME! * @param perpendicularStart DOCUMENT ME! * @param length DOCUMENT ME! * * @return DOCUMENT ME! */ private LineSegment leftyPerpendicular(final LineSegment segment, final Coordinate perpendicularStart, final double length) { return perpendicular(segment, perpendicularStart, length, true); } /** * DOCUMENT ME! * * @param segment DOCUMENT ME! * @param perpendicularStart DOCUMENT ME! * @param length DOCUMENT ME! * * @return DOCUMENT ME! */ private LineSegment rightyPerpendicular(final LineSegment segment, final Coordinate perpendicularStart, final double length) { return perpendicular(segment, perpendicularStart, length, false); } /** * DOCUMENT ME! * * @param trigger DOCUMENT ME! * @param feature sel DOCUMENT ME! * * @return DOCUMENT ME! */ private LineSegment getNearestSegment(final Coordinate trigger, final Feature feature) { LineSegment segment = null; double dist = Double.POSITIVE_INFINITY; final PFeature pfeature = (PFeature)mappingComponent.getPFeatureHM().get(feature); if (pfeature != null) { final Geometry geometry = pfeature.getFeature().getGeometry(); if ((geometry instanceof Polygon) || (geometry instanceof LineString) || (geometry instanceof MultiPolygon)) { for (int entityIndex = 0; entityIndex < pfeature.getNumOfEntities(); entityIndex++) { for (int ringIndex = 0; ringIndex < pfeature.getNumOfRings(entityIndex); ringIndex++) { final float[] xp = pfeature.getXp(entityIndex, ringIndex); final float[] yp = pfeature.getYp(entityIndex, ringIndex); for (int coordIndex = xp.length - 1; coordIndex > 0; coordIndex--) { final LineSegment tmpSegment = new LineSegment( xp[coordIndex - 1], yp[coordIndex - 1], xp[coordIndex], yp[coordIndex]); final double tmpDist = tmpSegment.distance(trigger); if (tmpDist < dist) { dist = tmpDist; segment = tmpSegment; } } } } } } return segment; } //~ Inner Classes ---------------------------------------------------------- /** * DOCUMENT ME! * * @version $Revision$, $Date$ */ class AddHandle extends PHandle { //~ Instance fields ---------------------------------------------------- private final PFeature pFeature; private final int entityPosition; private final int ringPosition; private final int coordPosition; private final Coordinate coordinate; private final LineSegment segment; //~ Constructors ------------------------------------------------------- /** * Creates a new AddHandle object. * * @param pFeature DOCUMENT ME! * @param entityPosition DOCUMENT ME! * @param ringPosition DOCUMENT ME! * @param coordPosition DOCUMENT ME! * @param coordinate DOCUMENT ME! * @param segment DOCUMENT ME! */ public AddHandle(final PFeature pFeature, final int entityPosition, final int ringPosition, final int coordPosition, final Coordinate coordinate, final LineSegment segment) { super(new PLocator() { @Override public double locateX() { return coordinate.x; } @Override public double locateY() { return coordinate.y; } }, mappingComponent); this.pFeature = pFeature; this.entityPosition = entityPosition; this.ringPosition = ringPosition; this.coordPosition = coordPosition; this.coordinate = coordinate; this.segment = segment; } //~ Methods ------------------------------------------------------------ @Override protected Color getDefaultColor() { return COLOR_PERPENDICULAR_HANDLE; } @Override public void handleClicked(final PInputEvent pInputEvent) { if (pInputEvent.getClickCount() == 2) { setSelected(true); finishAddHandles(); } else { if (pInputEvent.isLeftMouseButton()) { setSelected(!isSelected()); } } } /** * DOCUMENT ME! */ public void insertCoordinate() { final float handleX = (float)coordinate.x; final float handleY = (float)coordinate.y; pFeature.insertCoordinate(entityPosition, ringPosition, coordPosition, handleX, handleY); } } }