/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ package de.cismet.cismap.commons.gui.piccolo.eventlistener; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateSequence; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.PrecisionModel; import com.vividsolutions.jts.geom.impl.CoordinateArraySequence; import com.vividsolutions.jts.linearref.LengthIndexedLine; import com.vividsolutions.jts.linearref.LengthLocationMap; import com.vividsolutions.jts.linearref.LinearLocation; import com.vividsolutions.jts.linearref.LocationIndexedLine; import com.vividsolutions.jts.util.GeometricShapeFactory; import edu.umd.cs.piccolo.util.PDimension; import java.awt.Stroke; import java.awt.geom.Point2D; import java.text.Format; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.CopyOnWriteArrayList; import javax.swing.ImageIcon; import javax.swing.JComponent; import de.cismet.cismap.commons.CrsTransformer; import de.cismet.cismap.commons.Refreshable; import de.cismet.cismap.commons.features.DefaultStyledFeature; import de.cismet.cismap.commons.features.Feature; import de.cismet.cismap.commons.features.FeatureCollection; import de.cismet.cismap.commons.features.XStyledFeature; import de.cismet.cismap.commons.gui.MappingComponent; import de.cismet.cismap.commons.gui.piccolo.FeatureAnnotationSymbol; import de.cismet.cismap.commons.gui.piccolo.PFeature; import de.cismet.cismap.commons.interaction.CismapBroker; import de.cismet.math.geometry.StaticGeometryFunctions; /** * DOCUMENT ME! * * @author jruiz * @version $Revision$, $Date$ */ public class LinearReferencedPointFeature extends DefaultStyledFeature implements XStyledFeature, SelfManipulatingFeature { //~ Static fields/initializers --------------------------------------------- private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger( LinearReferencedPointFeature.class); public static final String PROPERTY_FEATURE_COORDINATE = "featureCoordinate"; //~ Instance fields -------------------------------------------------------- private Geometry baseLineGeom; private Collection<LinearReferencedPointFeatureListener> listeners = new ArrayList<LinearReferencedPointFeatureListener>(); private ImageIcon ico = new javax.swing.ImageIcon(LinearReferencedPointFeature.class.getResource( "/de/cismet/cismap/commons/gui/res/linRefPointIcon.png")); // NOI18N private ImageIcon annotationIco = new javax.swing.ImageIcon(getClass().getResource( "/de/cismet/cismap/commons/gui/res/linRefPoint.png")); // NOI18N private ImageIcon annotationSelectedIco = new javax.swing.ImageIcon(getClass().getResource( "/de/cismet/cismap/commons/gui/res/linRefPointSelected.png")); // NOI18N private Format infoFormat; private boolean isMovable = true; //~ Constructors ----------------------------------------------------------- /** * Creates a new LinearReferencedPointFeature object. * * @param value DOCUMENT ME! * @param baseLineGeom DOCUMENT ME! */ public LinearReferencedPointFeature(final double value, final Geometry baseLineGeom) { this(value, baseLineGeom, true); } /** * Creates a new LinearReferencedPointFeature object. * * @param value DOCUMENT ME! * @param baseLineGeom DOCUMENT ME! * @param showSubLine DOCUMENT ME! */ public LinearReferencedPointFeature(final double value, final Geometry baseLineGeom, final boolean showSubLine) { this.baseLineGeom = baseLineGeom; setGeometry(getPointOnLine(value, baseLineGeom)); setPointAnnotationSymbol(FeatureAnnotationSymbol.newCenteredFeatureAnnotationSymbol( annotationIco.getImage(), annotationSelectedIco.getImage())); } //~ Methods ---------------------------------------------------------------- /** * DOCUMENT ME! * * @param isMovable DOCUMENT ME! */ public void setMovable(final boolean isMovable) { this.isMovable = isMovable; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public boolean isMovable() { return isMovable; } /** * DOCUMENT ME! * * @param infoFormat DOCUMENT ME! */ public void setInfoFormat(final Format infoFormat) { this.infoFormat = infoFormat; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public Format getInfoFormat() { return infoFormat; } /** * DOCUMENT ME! * * @param ico DOCUMENT ME! */ public void setIconImage(final ImageIcon ico) { this.ico = ico; final MappingComponent mc = CismapBroker.getInstance().getMappingComponent(); final PFeature pFeature = mc.getPFeatureHM().get(this); if (pFeature != null) { pFeature.refresh(); } } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public Geometry getLineGeometry() { return baseLineGeom; } /** * DOCUMENT ME! * * @param listener DOCUMENT ME! */ public void addListener(final LinearReferencedPointFeatureListener listener) { listeners.add(listener); } /** * DOCUMENT ME! * * @param listener DOCUMENT ME! */ public void removeListener(final LinearReferencedPointFeatureListener listener) { listeners.remove(listener); } /** * DOCUMENT ME! * * @param coord DOCUMENT ME! * @param lineGeom DOCUMENT ME! * * @return DOCUMENT ME! */ public static Coordinate getNearestCoordninateOnLine(final Coordinate coord, final Geometry lineGeom) { final Coordinate[] neighbours = getNearestNeighbours(coord, lineGeom); if (neighbours != null) { final Point2D point = StaticGeometryFunctions.createPointOnLine( new Point2D.Double(neighbours[0].x, neighbours[0].y), new Point2D.Double(neighbours[1].x, neighbours[1].y), new Point2D.Double(coord.x, coord.y)); return new Coordinate(point.getX(), point.getY()); } else { return null; } } /** * DOCUMENT ME! * * @param coord DOCUMENT ME! * @param lineGeom DOCUMENT ME! * * @return DOCUMENT ME! */ public static double getDistanceOfCoordToLine(final Coordinate coord, final Geometry lineGeom) { final Coordinate[] neighbours = getNearestNeighbours(coord, lineGeom); if (neighbours != null) { final double distance = StaticGeometryFunctions.distanceToLine( new Point2D.Double(neighbours[0].x, neighbours[0].y), new Point2D.Double(neighbours[1].x, neighbours[1].y), new Point2D.Double(coord.x, coord.y)); return distance; } else { return -1d; } } /** * DOCUMENT ME! * * @param coordinate DOCUMENT ME! * @param delta DOCUMENT ME! */ @Override public void moveTo(final Coordinate coordinate, final PDimension delta) { if (isMovable()) { // // mauskoordinaten ins selbe coordsys umwandeln wie das der route // coordinate = transformToRouteSrid(coordinate); final Geometry cuttedLineGeom = getReducedLineGeometry( baseLineGeom, getGeometry().getCoordinate(), coordinate); final Coordinate manipulatedCoordinate = getNearestCoordninateOnLine(coordinate, cuttedLineGeom); if (manipulatedCoordinate != null) { performMove(manipulatedCoordinate); } } } /** * DOCUMENT ME! * * @param lineGeom DOCUMENT ME! * @param lastCoordinate DOCUMENT ME! * @param newCoordinate DOCUMENT ME! * * @return DOCUMENT ME! */ public static Geometry getReducedLineGeometry(final Geometry lineGeom, final Coordinate lastCoordinate, final Coordinate newCoordinate) { // Kreisgeometrie errechnen um die Suche nach den nächsten Nachbarpunkten // auf einer Teillinie einzuschränken, statt auf der gesamten Linie. final GeometricShapeFactory gsf = new GeometricShapeFactory(); // Zentrum auf der Koordinate von der aus gesucht werden soll. gsf.setCentre(newCoordinate); // Umfang des Kreises = doppelter Abstand zur jetzigen Koordinate des Punktes gsf.setSize(newCoordinate.distance(lastCoordinate) * 2); final Geometry circleGeom = gsf.createCircle(); // Teillinie aus Verschnitt mit dem Kreis erstellen return lineGeom.intersection(circleGeom); } /** * DOCUMENT ME! * * @param coord DOCUMENT ME! * * @return DOCUMENT ME! */ private Coordinate transformToRouteSrid(final Coordinate coord) { try { final CrsTransformer crsT = new CrsTransformer(CrsTransformer.createCrsFromSrid( getLineGeometry().getSRID())); final CoordinateSequence coordSeq = new CoordinateArraySequence(new Coordinate[] { coord }); final Point point = new Point(coordSeq, getLineGeometry().getFactory()); final Point transformedPoint = crsT.transformGeometry(point, CismapBroker.getInstance().getSrs().getCode()); return transformedPoint.getCoordinate(); } catch (Exception ex) { LOG.error("Fehler beim Umrechnen des CRS", ex); } return coord; } /** * DOCUMENT ME! * * @param coordinate DOCUMENT ME! */ private void performMove(final Coordinate coordinate) { final MappingComponent mc = CismapBroker.getInstance().getMappingComponent(); final PFeature pFeature = mc.getPFeatureHM().get(this); if (pFeature != null) { pFeature.setCoordArr(0, 0, new Coordinate[] { (Coordinate)coordinate.clone() }); pFeature.updatePath(); pFeature.syncGeometry(); pFeature.resetInfoNodePosition(); pFeature.visualize(); fireFeatureMoved(); } } /** * DOCUMENT ME! */ private void fireFeatureMoved() { final Collection<LinearReferencedPointFeatureListener> listenersCopy = new CopyOnWriteArrayList<LinearReferencedPointFeatureListener>(listeners); for (final LinearReferencedPointFeatureListener listener : listenersCopy) { listener.featureMoved(this); } } /** * DOCUMENT ME! * * @param mergePoint DOCUMENT ME! * @param withPoint DOCUMENT ME! */ private void fireFeatureMerged(final LinearReferencedPointFeature mergePoint, final LinearReferencedPointFeature withPoint) { final Collection<LinearReferencedPointFeatureListener> listenersCopy = new CopyOnWriteArrayList<LinearReferencedPointFeatureListener>(listeners); for (final LinearReferencedPointFeatureListener listener : listenersCopy) { listener.featureMerged(mergePoint, withPoint); } } /** * DOCUMENT ME! * * @param position DOCUMENT ME! */ public void moveToPosition(final double position) { final Coordinate coordinate = getCoordinateOnLine(position, baseLineGeom); performMove(coordinate); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public double getCurrentPosition() { final Coordinate coord = getGeometry().getCoordinate(); final double cursorX = coord.x; final double cursorY = coord.y; if (baseLineGeom != null) { final LocationIndexedLine lil = new LocationIndexedLine(baseLineGeom); final Coordinate c = new Coordinate(cursorX, cursorY); final LinearLocation ll = lil.indexOf(c); final LengthLocationMap llm = new LengthLocationMap(baseLineGeom); return llm.getLength(ll); } else { return 0d; } } /** * Sucht die Koordinaten der 2 nächsten Punkten der Linie von der Koordinate eines bestimmten Punktes aus. * * @param coord DOCUMENT ME! * @param lineGeom DOCUMENT ME! * * @return DOCUMENT ME! */ public static Coordinate[] getNearestNeighbours(final Coordinate coord, final Geometry lineGeom) { Coordinate start = null; Coordinate end = null; double dist = Double.POSITIVE_INFINITY; // Suche auf Teillinie final Coordinate[] coords = lineGeom.getCoordinates(); for (int i = 0; i < (coords.length - 1); i++) { final Coordinate tmpStart = coords[i]; final Coordinate tmpEnd = coords[i + 1]; final double tmpDist = StaticGeometryFunctions.distanceToLine( new Point2D.Double(tmpStart.x, tmpStart.y), new Point2D.Double(tmpEnd.x, tmpEnd.y), new Point2D.Double(coord.x, coord.y)); if (tmpDist < dist) { dist = tmpDist; start = tmpStart; end = tmpEnd; } } if ((start != null) && (end != null)) { return new Coordinate[] { start, end }; } else { return null; } } /** * DOCUMENT ME! * * @param pointCoord DOCUMENT ME! * @param linestringOrMultilinestring DOCUMENT ME! * * @return DOCUMENT ME! */ public static double getPositionOnLine(final Coordinate pointCoord, final Geometry linestringOrMultilinestring) { final LocationIndexedLine lineLIL = new LocationIndexedLine(linestringOrMultilinestring); final LengthLocationMap lineLLM = new LengthLocationMap(linestringOrMultilinestring); final LinearLocation pointLL = lineLIL.indexOf(pointCoord); final double pointPosition = lineLLM.getLength(pointLL); return pointPosition; } /** * DOCUMENT ME! * * @param position DOCUMENT ME! * @param linestringOrMultilinestring DOCUMENT ME! * * @return DOCUMENT ME! */ private static Coordinate getCoordinateOnLine(final double position, final Geometry linestringOrMultilinestring) { final LengthIndexedLine lil = new LengthIndexedLine(linestringOrMultilinestring); final Coordinate coordinate = lil.extractPoint(position); return coordinate; } /** * DOCUMENT ME! * * @param position DOCUMENT ME! * @param linestringOrMultilinestring DOCUMENT ME! * * @return DOCUMENT ME! */ public static Geometry getPointOnLine(final double position, final Geometry linestringOrMultilinestring) { final Coordinate coordinate = getCoordinateOnLine(position, linestringOrMultilinestring); return new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), linestringOrMultilinestring.getSRID()) .createPoint(coordinate); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ @Override public ImageIcon getIconImage() { return ico; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ @Override public String getName() { return "Station"; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ @Override public String getType() { return "Station"; } /** * DOCUMENT ME! * * @param refresh DOCUMENT ME! * * @return DOCUMENT ME! */ @Override public JComponent getInfoComponent(final Refreshable refresh) { return null; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ @Override public Stroke getLineStyle() { return null; } /** * DOCUMENT ME! */ @Override public void moveFinished() { final LinearReferencedPointFeature snappingPoint = getSnappingPoint(); if (snappingPoint != null) { fireFeatureMerged(this, snappingPoint); snappingPoint.fireFeatureMerged(this, snappingPoint); } } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public LinearReferencedPointFeature getSnappingPoint() { final boolean snapping = CismapBroker.getInstance().getMappingComponent().isSnappingEnabled(); if (snapping) { final FeatureCollection fc = CismapBroker.getInstance().getMappingComponent().getFeatureCollection(); final Feature[] features = fc.getAllFeatures().toArray(new Feature[0]); // nicht die originalcollection, // weil diese sich in der // schleife verändern kann final LinearReferencedPointFeature mergePoint = this; for (final Feature feature : features) { if ((feature instanceof LinearReferencedPointFeature) && (feature != mergePoint)) { final LinearReferencedPointFeature withPoint = (LinearReferencedPointFeature)feature; final boolean isInSnappingDistance = Math.abs(withPoint.getCurrentPosition() - mergePoint.getCurrentPosition()) < (0.002 * CismapBroker.getInstance().getMappingComponent().getScaleDenominator()); if (isInSnappingDistance) { return withPoint; } } } } return null; } }