/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ package de.cismet.cismap.commons.tools; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineSegment; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geom.PrecisionModel; import edu.umd.cs.piccolo.PLayer; import edu.umd.cs.piccolo.PNode; 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.piccolo.util.PPickPath; import java.awt.Rectangle; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.*; import de.cismet.cismap.commons.CrsTransformer; import de.cismet.cismap.commons.WorldToScreenTransform; import de.cismet.cismap.commons.gui.MappingComponent; import de.cismet.cismap.commons.gui.piccolo.PFeature; import de.cismet.cismap.commons.gui.piccolo.ParentNodeIsAPFeature; import de.cismet.cismap.commons.interaction.CismapBroker; /** * DOCUMENT ME! * * @author thorsten.hell@cismet.de * @version $Revision$, $Date$ */ public class PFeatureTools { //~ Static fields/initializers --------------------------------------------- private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger( "de.cismet.cismap.commons.tools.PFeatureTools"); // NOI18N //~ Methods ---------------------------------------------------------------- /** * DOCUMENT ME! * * @param pInputEvent DOCUMENT ME! * @param validClasses DOCUMENT ME! * * @return DOCUMENT ME! */ public static PNode getFirstValidObjectUnderPointer2(final PInputEvent pInputEvent, final Class[] validClasses) { return getFirstValidObjectUnderPointer2(pInputEvent, validClasses, 0); } /** * DOCUMENT ME! * * @param pInputEvent DOCUMENT ME! * @param validClasses DOCUMENT ME! * @param halo DOCUMENT ME! * * @return DOCUMENT ME! */ public static PNode getFirstValidObjectUnderPointer2(final PInputEvent pInputEvent, final Class[] validClasses, final double halo) { final List<PNode> allValids = (List<PNode>)getFirstObjectsUnderPointer(pInputEvent, validClasses, halo); if (allValids.isEmpty()) { return null; } return allValids.get(0); } /** * DOCUMENT ME! * * @param pInputEvent DOCUMENT ME! * @param validClasses DOCUMENT ME! * @param halo DOCUMENT ME! * * @return DOCUMENT ME! */ public static List<PNode> getFirstObjectsUnderPointer(final PInputEvent pInputEvent, final Class[] validClasses, final double halo) { return getValidObjectsUnderPointer(pInputEvent, validClasses, halo, true); } /** * DOCUMENT ME! * * @param pInputEvent DOCUMENT ME! * @param validClasses DOCUMENT ME! * @param halo DOCUMENT ME! * * @return DOCUMENT ME! */ public static List<PNode> getValidObjectsUnderPointer(final PInputEvent pInputEvent, final Class[] validClasses, final double halo) { return getValidObjectsUnderPointer(pInputEvent, validClasses, halo, false); } /** * DOCUMENT ME! * * @param pInputEvent mc DOCUMENT ME! * @param validClasses DOCUMENT ME! * @param halo DOCUMENT ME! * @param stopAfterFirstValid DOCUMENT ME! * * @return DOCUMENT ME! */ public static List<PNode> getValidObjectsUnderPointer(final PInputEvent pInputEvent, final Class[] validClasses, final double halo, final boolean stopAfterFirstValid) { final MappingComponent mc = (MappingComponent)pInputEvent.getComponent(); final WorldToScreenTransform wtst = mc.getWtst(); final int srs = CrsTransformer.extractSridFromCrs(mc.getMappingModel().getSrs().getCode()); final GeometryFactory gf = new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), srs); final double x1 = wtst.getWorldX(pInputEvent.getPosition().getX()); final double y1 = wtst.getWorldY(pInputEvent.getPosition().getY()); final Geometry point; if (halo > 0) { point = gf.createPoint(new Coordinate(x1, y1)).buffer(halo * mc.getScaleDenominator()); } else { point = gf.createPoint(new Coordinate(x1, y1)); } final List<PNode> pNodes = new ArrayList<PNode>(findIntersectingPFeatures(mc.getFeatureLayer(), point)); for (int i = 0; i < mc.getMapServiceLayer().getChildrenCount(); ++i) { final PNode pNode = mc.getMapServiceLayer().getChild(i); if (pNode instanceof PLayer) { pNodes.addAll(findIntersectingPFeatures(pNode, point)); } } final LinkedList<PNode> allValidPNodes = new LinkedList<PNode>(); for (final PNode pNode : pNodes) { for (int i = 0; i < validClasses.length; ++i) { if ((pNode != null) && validClasses[i].isAssignableFrom(pNode.getClass()) && (pNode.getParent() != null) && pNode.getParent().getVisible() && pNode.getVisible()) { allValidPNodes.add(pNode); if (stopAfterFirstValid) { break; } } } } return allValidPNodes; } /** * DOCUMENT ME! * * @param mc DOCUMENT ME! * @param bounds DOCUMENT ME! * * @return DOCUMENT ME! */ public static PFeature[] getPFeaturesInArea(final MappingComponent mc, final PBounds bounds) { final WorldToScreenTransform wtst = mc.getWtst(); final Geometry bBox = getGeometryFromPBounds(bounds, wtst, mc.getMappingModel().getSrs().getCode()); return getPFeaturesInArea(mc, bBox); } /** * DOCUMENT ME! * * @param mc DOCUMENT ME! * @param geom bounds DOCUMENT ME! * * @return DOCUMENT ME! */ public static PFeature[] getPFeaturesInArea(final MappingComponent mc, final Geometry geom) { final List<PFeature> pFeatures = findIntersectingPFeatures(mc.getFeatureLayer(), geom); for (int i = 0; i < mc.getMapServiceLayer().getChildrenCount(); ++i) { final PNode p = mc.getMapServiceLayer().getChild(i); if (p instanceof PLayer) { pFeatures.addAll(findIntersectingPFeatures(mc.getMapServiceLayer().getChild(i), geom)); } } final Collection<PFeature> vRet = new ArrayList<PFeature>(pFeatures.size()); for (final PFeature pf : pFeatures) { if (pf.isSnappable()) { vRet.add(pf); } } return vRet.toArray(new PFeature[0]); } /** * DOCUMENT ME! * * @param bounds a PBounds object, that should contain screen coordinates. (See the class WorldToScreenTransform) * @param wtst DOCUMENT ME! * @param crs DOCUMENT ME! * * @return a Geometry object that represents the given PBounds object. */ public static Geometry getGeometryFromPBounds(final PBounds bounds, final WorldToScreenTransform wtst, final String crs) { final int srs = CrsTransformer.extractSridFromCrs(crs); final GeometryFactory gf = new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), srs); final double x1 = wtst.getWorldX(bounds.x); final double x2 = wtst.getWorldX(bounds.x + bounds.width); final double y1 = wtst.getWorldY(bounds.y); final double y2 = wtst.getWorldY(bounds.y + bounds.height); final Coordinate[] polyCords = new Coordinate[5]; polyCords[0] = new Coordinate(x1, y1); polyCords[1] = new Coordinate(x1, y2); polyCords[2] = new Coordinate(x2, y2); polyCords[3] = new Coordinate(x2, y1); polyCords[4] = new Coordinate(x1, y1); return gf.createPolygon(gf.createLinearRing(polyCords), null); } /** * This should be used instead of the findIntersectingNodes method of the PNode class. The differences between this * methods and the findIntersectingNodes method are, that this method only finds PFeature objects and this method * works properly. * * @param node The node, the PFeatures should be find in * @param geometry the search geometry * * @return DOCUMENT ME! */ public static List<PFeature> findIntersectingPFeatures(final PNode node, final Geometry geometry) { final List<PFeature> pFeatures = new ArrayList<PFeature>(); final String srs = CrsTransformer.createCrsFromSrid(geometry.getSRID()); for (int index = 0; index < node.getChildrenCount(); index++) { final PNode pNode = node.getChild(index); if (pNode instanceof PFeature) { final PFeature pFeature = (PFeature)pNode; Geometry featureGeometry = pFeature.getFeature().getGeometry(); if (featureGeometry.getSRID() != geometry.getSRID()) { featureGeometry = CrsTransformer.transformToGivenCrs(featureGeometry, srs); } if (intersects(featureGeometry, geometry)) { pFeatures.add(pFeature); } } else { pFeatures.addAll(findIntersectingPFeatures(pNode, geometry)); } } return pFeatures; } /** * Determines whether g1 and g2 intersects. Contrary to the intersects method of the Geometry class, this method * does also support GeometryCollections. * * @param g1 DOCUMENT ME! * @param g2 DOCUMENT ME! * * @return true, iff g1 intersects g2 */ private static boolean intersects(Geometry g1, Geometry g2) { if (g2 instanceof GeometryCollection) { final Geometry tmp = g1; g1 = g2; g2 = tmp; } if ((g1 instanceof GeometryCollection) && (g2 instanceof GeometryCollection)) { final GeometryCollection gc1 = (GeometryCollection)g1; final GeometryCollection gc2 = (GeometryCollection)g2; for (int i = 0; i < gc1.getNumGeometries(); ++i) { for (int n = 0; n < gc2.getNumGeometries(); ++n) { final Geometry geomEntry1 = gc1.getGeometryN(i); final Geometry geomEntry2 = gc2.getGeometryN(n); if (intersects(geomEntry1, geomEntry2)) { return true; } } } return false; } else if (g1 instanceof GeometryCollection) { final GeometryCollection gc = (GeometryCollection)g1; for (int i = 0; i < gc.getNumGeometries(); ++i) { final Geometry geomEntry = gc.getGeometryN(i); if (intersects(geomEntry, g2)) { return true; } } return false; } else { return g1.intersects(g2); } } /** * DOCUMENT ME! * * @param mc DOCUMENT ME! * @param bounds DOCUMENT ME! * * @return DOCUMENT ME! */ public static Point2D[] getPointsInArea(final MappingComponent mc, final PBounds bounds) { final PFeature[] features = getPFeaturesInArea(mc, bounds); final Collection<Point2D> points = new ArrayList<Point2D>(); if (features == null) { return null; } for (final PFeature pfeature : features) { 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 position = 0; position < xp.length; position++) { if (bounds.contains(xp[position], yp[position])) { points.add(new Point2D.Float(xp[position], yp[position])); } } } } } return points.toArray(new Point2D[0]); } /** * DOCUMENT ME! * * @param mc DOCUMENT ME! * @param bounds DOCUMENT ME! * @param myPosition DOCUMENT ME! * @param vetoPoint DOCUMENT ME! * * @return DOCUMENT ME! */ private static Point2D getNearestPointInArea(final MappingComponent mc, final PBounds bounds, final Point2D myPosition, final Point2D vetoPoint) { final Point2D[] points = getPointsInArea(mc, bounds); double distance = -1; Point2D nearestPoint = null; for (int i = 0; i < points.length; ++i) { if ((vetoPoint != null) && points[i].equals(vetoPoint)) { return null; } final double distanceCheck = myPosition.distanceSq(points[i]); if ((distance < 0) || (distanceCheck < distance)) { nearestPoint = points[i]; distance = distanceCheck; } } return nearestPoint; } /** * DOCUMENT ME! * * @param mc DOCUMENT ME! * @param canvasPosition DOCUMENT ME! * * @return DOCUMENT ME! */ public static Point2D getNearestPointInArea(final MappingComponent mc, final Point2D canvasPosition) { return getNearestPointInArea(mc, canvasPosition, null); } /** * DOCUMENT ME! * * @param mc DOCUMENT ME! * @param canvasPosition DOCUMENT ME! * @param vertexRequired DOCUMENT ME! * @param considerVetoObjects veto objects are objects, which should be ignored from the snapping mechanism. This * can be the currently modifying feature. * * @return DOCUMENT ME! */ public static Point2D getNearestPointInArea(final MappingComponent mc, final Point2D canvasPosition, final boolean vertexRequired, final boolean considerVetoObjects) { final Point2D vetoPoint = (considerVetoObjects ? CismapBroker.getInstance().getSnappingVetoPoint() : null); if (!vertexRequired) { return getNearestPointInArea(mc, canvasPosition, vetoPoint); } else { final PFeature vetoFeature = (considerVetoObjects ? CismapBroker.getInstance().getSnappingVetoFeature() : null); return getNearestPointInAreaNoVertexRequired( mc, canvasPosition, vetoPoint, vetoFeature); } } /** * DOCUMENT ME! * * @param mc DOCUMENT ME! * @param canvasPosition DOCUMENT ME! * @param vetoPoint DOCUMENT ME! * @param vetoFeature DOCUMENT ME! * * @return DOCUMENT ME! */ public static Point2D getNearestPointInAreaNoVertexRequired(final MappingComponent mc, final Point2D canvasPosition, final Point2D vetoPoint, final PFeature vetoFeature) { final Rectangle2D area = new Rectangle((int)canvasPosition.getX() - (mc.getSnappingRectSize() / 2), (int)canvasPosition.getY() - (mc.getSnappingRectSize() / 2), mc.getSnappingRectSize(), mc.getSnappingRectSize()); final Rectangle2D d2d = mc.getCamera().localToView(new PBounds(area)); final Point2D myPosition = mc.getCamera().localToView(canvasPosition); final PBounds bounds = new PBounds(d2d); final Point2D[] points = getPointsInAreaNoVertexRequired(mc, bounds, myPosition, vetoFeature); double distance = -1; Point2D nearestPoint = null; for (int i = 0; i < points.length; ++i) { final double distanceCheck = myPosition.distanceSq(points[i]); if (((vetoPoint == null) || !vetoPoint.equals(points[i])) && ((distance < 0) || (distanceCheck < distance))) { nearestPoint = points[i]; distance = distanceCheck; } } return nearestPoint; } /** * DOCUMENT ME! * * @param mc DOCUMENT ME! * @param bounds DOCUMENT ME! * @param currentPosition DOCUMENT ME! * @param vetoFeature DOCUMENT ME! * * @return DOCUMENT ME! */ public static Point2D[] getPointsInAreaNoVertexRequired(final MappingComponent mc, final PBounds bounds, final Point2D currentPosition, final PFeature vetoFeature) { final PFeature[] features = getPFeaturesInArea(mc, bounds); final Collection<Point2D> points = new ArrayList<Point2D>(); if (features == null) { return null; } final Coordinate c = new Coordinate(currentPosition.getX(), currentPosition.getY()); for (final PFeature pfeature : features) { if (!pfeature.equals(vetoFeature)) { final LineSegment seg = getNearestSegment(c, pfeature); final Coordinate point = seg.closestPoint(c); if (bounds.contains(point.x, point.y)) { points.add(new Point2D.Float((float)point.x, (float)point.y)); } } } return points.toArray(new Point2D[0]); } /** * DOCUMENT ME! * * @param trigger DOCUMENT ME! * @param pfeature DOCUMENT ME! * * @return DOCUMENT ME! */ public static LineSegment getNearestSegment(final Coordinate trigger, final PFeature pfeature) { LineSegment segment = null; double dist = Double.POSITIVE_INFINITY; 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; } /** * DOCUMENT ME! * * @param mc DOCUMENT ME! * @param canvasPosition DOCUMENT ME! * @param vetoPoint DOCUMENT ME! * * @return DOCUMENT ME! */ public static Point2D getNearestPointInArea(final MappingComponent mc, final Point2D canvasPosition, final Point2D vetoPoint) { final Rectangle2D area = new Rectangle((int)canvasPosition.getX() - (mc.getSnappingRectSize() / 2), (int)canvasPosition.getY() - (mc.getSnappingRectSize() / 2), mc.getSnappingRectSize(), mc.getSnappingRectSize()); final Rectangle2D d2d = mc.getCamera().localToView(new PBounds(area)); final Point2D myPosition = mc.getCamera().localToView(canvasPosition); return getNearestPointInArea(mc, new PBounds(d2d), myPosition, vetoPoint); } /** * DOCUMENT ME! * * @param pInputEvent DOCUMENT ME! * @param validClasses DOCUMENT ME! * * @return DOCUMENT ME! */ public static PNode getFirstValidObjectUnderPointer(final PInputEvent pInputEvent, final Class[] validClasses) { return getFirstValidObjectUnderPointer(pInputEvent, validClasses, 1d); } /** * DOCUMENT ME! * * @param pInputEvent DOCUMENT ME! * @param validClasses DOCUMENT ME! * @param halo DOCUMENT ME! * * @return DOCUMENT ME! */ public static PNode getFirstValidObjectUnderPointer(final PInputEvent pInputEvent, final Class[] validClasses, final double halo) { // Dieses Konstrukt sorgt daf\u00FCr das uninteressante Objekte die oberhalb dem Mauszeiger liegen // einfach ignoriert werden PNode pNode = null; boolean rightType = false; boolean first = true; // PPickPath pp=pInputEvent.getInputManager().getMouseOver(); final double xPos = pInputEvent.getPosition().getX(); final double yPos = pInputEvent.getPosition().getY(); final PPickPath pp = ((MappingComponent)pInputEvent.getComponent()).getCamera() .pick(pInputEvent.getCanvasPosition().getX(), pInputEvent.getCanvasPosition().getY(), halo); pp.pushNode(pInputEvent.getPickedNode()); do { if (first) { pNode = pp.getPickedNode(); first = false; } else { pNode = pp.nextPickedNode(); } // if (o!=null && o instanceof PPath && !((PPath)o).getPathReference().contains(xPos,yPos)) { // //In diesem Fall handelte es sich zwar um ein PPATH aber x,y war nicht im PPath enthalten, deshalb mach nix // // } else // durch dieses if wird genaues selektieren erreicht { for (int i = 0; i < validClasses.length; ++i) { // if (o!=null) log.debug("_ getFirstValidObjectUnderPointer teste "+o.getClass()+ ":"+validClasses[i].getName()+" :"+ validClasses[i].isAssignableFrom(o.getClass())); if ((pNode != null) && validClasses[i].isAssignableFrom(pNode.getClass()) && (pNode.getParent() != null) && pNode.getParent().getVisible() && pNode.getVisible()) { if ((pNode instanceof PPath) && (!isPolygon((PPath)pNode) || ((PPath)pNode).getPathReference().contains(xPos, yPos))) { rightType = true; break; } } else if ((validClasses[i] == PFeature.class) && (pNode != null) && ParentNodeIsAPFeature.class.isAssignableFrom(pNode.getClass()) && (pNode.getParent() != null) && pNode.getParent().getVisible() && pNode.getVisible()) { pNode = getPFeatureByChild((ParentNodeIsAPFeature)pNode); if (pNode != null) { rightType = true; break; } } } } } while ((pNode != null) && !rightType); return pNode; } /** * Checks, if the given geometry contains a polygon. * * @param o the Geometry to check * * @return true, iff the given PPath is an instanec of PPfeature and contains a Polygon or Multipolygon */ private static boolean isPolygon(final PPath o) { if ((o instanceof PFeature) && (((PFeature)o).getFeature() != null)) { final PFeature feature = (PFeature)o; return (feature.getFeature().getGeometry() instanceof Polygon) || (feature.getFeature().getGeometry() instanceof MultiPolygon); } return false; } /** * DOCUMENT ME! * * @param pInputEvent DOCUMENT ME! * @param validClasses DOCUMENT ME! * * @return DOCUMENT ME! */ public static List<PNode> getAllValidObjectsUnderPointer(final PInputEvent pInputEvent, final Class[] validClasses) { return getValidObjectsUnderPointer(pInputEvent, validClasses, 0.001d, false); } /** * DOCUMENT ME! * * @param child DOCUMENT ME! * * @return DOCUMENT ME! * * @throws IllegalArgumentException DOCUMENT ME! */ public static PFeature getPFeatureByChild(final ParentNodeIsAPFeature child) { final PNode parent = ((PNode)child).getParent(); if (parent instanceof PFeature) { return (PFeature)parent; } else if (parent instanceof ParentNodeIsAPFeature) { return getPFeatureByChild((ParentNodeIsAPFeature)parent); } else { throw new IllegalArgumentException("ParentNodeIsAPFeature " + child + " has no ParentNode that is a PFeature"); // NOI18N } } /** * TODO move to a static geometryutils class. * * @param pfeature DOCUMENT ME! * * @return DOCUMENT ME! */ public static Point2D centroid(final PFeature pfeature) { double cx = 0; double cy = 0; // TODO centroid wirklich über alle ringe berechnen ? 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); final int n = xp.length; for (int i = 0; i < n; i++) { final int j = (i + 1) % n; final double factor = (xp[i] * yp[j]) - (xp[j] * yp[i]); cx += (xp[i] + xp[j]) * factor; cy += (yp[i] + yp[j]) * factor; } final double factor = 1 / (6.0f * area(pfeature)); cx *= factor; cy *= factor; } } return new Point2D.Double(cx, cy); } /** * TODO move to a static geometryutils class. * * @param pfeature DOCUMENT ME! * * @return DOCUMENT ME! */ public static double area(final PFeature pfeature) { double areaTotal = 0; 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); final int n = xp.length; double area = 0; for (int i = 0; i < n; i++) { final int j = (i + 1) % n; area += xp[i] * yp[j]; area -= xp[j] * yp[i]; } area /= 2f; if (ringIndex == 0) { // polygon außenhülle areaTotal += area; } else { // loch areaTotal -= area; } } } return areaTotal; } }