/** * */ package com.tilusnet.josm.plugins.alignways; import static org.openstreetmap.josm.tools.I18n.tr; import java.util.ArrayList; import java.util.Map; import javax.swing.JOptionPane; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.WaySegment; import com.tilusnet.josm.plugins.alignways.geometry.AlignWaysGeomLine; import com.tilusnet.josm.plugins.alignways.geometry.AlignWaysGeomLine.IntersectionStatus; import com.tilusnet.josm.plugins.alignways.geometry.AlignWaysGeomPoint; /** * @author tilusnet <tilusnet@gmail.com> * */ public class AlignWaysCmdKeepAngles extends AlignWaysCmdKeepLength { private AlignableStatus alignableStatKeepAngles = AlignableStatus.ALGN_VALID; public AlignWaysCmdKeepAngles() { // Now the calculatedNodes reflect the coordinates that we'd have // without preserving the angles, i.e. preserving the length. Map<Node,EastNorth> calcNodesKeepLength = calculatedNodes; // Now we'll proceed with the hypothetical recalculation of the endpoint coordinates // following the rule of preserving angles instead. The new nodes will be stored in nodeArr[]. Node[] nodeArr = algnSeg.getSegmentEndPoints().toArray(new Node[2]); EastNorth enCalc1 = calcNodesKeepLength.get(nodeArr[0]); EastNorth enCalc2 = calcNodesKeepLength.get(nodeArr[1]); AlignWaysGeomLine lineKeepLength = new AlignWaysGeomLine(enCalc1.getX(), enCalc1.getY(), enCalc2.getX(), enCalc2.getY()); recalculateNodesAndValidate(lineKeepLength, nodeArr[0]); recalculateNodesAndValidate(lineKeepLength, nodeArr[1]); } private void recalculateNodesAndValidate(AlignWaysGeomLine alignedLineKeepLength, Node endpoint) { if (endpoint.getEastNorth().equals(pivot)) { // endpoint is pivot: the coordinates won't change return; } ArrayList<WaySegment> alws = algnSeg.getAdjacentWaySegments(endpoint); int alwsSize = alws.size(); if (0 < alwsSize && alwsSize <= 2) { // We need the intersection point of // - the alignee following the keep length rule // - the adjacent way segment Node adjOther1 = getNonEqualNode(alws.get(0), endpoint); EastNorth enAdjOther1 = adjOther1.getEastNorth(); Node adjOther2 = null; EastNorth enAdjOther2 = null; if (alwsSize == 2) { adjOther2 = getNonEqualNode(alws.get(1), endpoint); enAdjOther2 = adjOther2.getEastNorth(); // In order have a chance to align, (enAdjOther1, enAdjOther2 and endpoint) must be collinear ArrayList<EastNorth> enAdjPts = new ArrayList<>(3); enAdjPts.add(enAdjOther1); enAdjPts.add(endpoint.getEastNorth()); enAdjPts.add(enAdjOther2); if (!isEnSetCollinear(enAdjPts)) { // Not collinear, no point to proceed alignableStatKeepAngles = AlignableStatus.ALGN_INV_ANGLE_PRESERVING_CONFLICT; return; } } // Update the calculated node for angle preserving alignment AlignWaysGeomPoint isectPnt = alignedLineKeepLength.getIntersection(new AlignWaysGeomLine(enAdjOther1.getX(), enAdjOther1.getY(), endpoint.getEastNorth().getX(), endpoint.getEastNorth().getY())); EastNorth enIsectPt = null; // If the intersection is null, the adjacent and the alignee are parallel already: // there's no need to update this node if (isectPnt != null) { enIsectPt = new EastNorth(isectPnt.getX(), isectPnt.getY()); // Don't "record" it yet as it may not be valid } else if (alignedLineKeepLength.getIntersectionStatus() == IntersectionStatus.LINES_PARALLEL) { alignableStatKeepAngles = AlignableStatus.ALGN_INV_ANGLE_PRESERVING_CONFLICT; return; } // For the case of two adjacent segments with collinear points, the new endpoint may // not fall between enAdjOther1 and enAdjOther2; // this scenario is not allowed for the time being as placing the new intersection point on the line // triggers complications. // TODO - find a solution if (alwsSize == 2 && enIsectPt != null) { int middlePtIdx = AlignWaysGeomPoint.getMiddleOf3( new AlignWaysGeomPoint(enIsectPt), new AlignWaysGeomPoint(enAdjOther1), new AlignWaysGeomPoint(enAdjOther2)); if (middlePtIdx != 0) { EastNorth middlePt = null; switch(middlePtIdx) { case 1: middlePt = enIsectPt; break; case 2: middlePt = enAdjOther1; break; case 3: middlePt = enAdjOther2; break; } if (middlePt != null) { double eps = 1E-6; if (!middlePt.equalsEpsilon(enIsectPt, eps)) { // Intersection point didn't fall between the two adjacent points; not allowed alignableStatKeepAngles = AlignableStatus.ALGN_INV_XPOINT_FALLSOUT; return; /* if (middlePt.equalsEpsilon(enAdjOther1, eps)) { // Delete adjOther1 // adjOther1.setDeleted(true); } else if (true); // Delete adjOther2 // adjOther2.setDeleted(true); */ } } } } if (isectPnt != null) { // Angle preserving alignment passed all verification tests: record it. calculatedNodes.put(endpoint, enIsectPt); } } else { // angle preserving alignment not possible alignableStatKeepAngles = AlignableStatus.ALGN_INV_TOOMANY_CONNECTED_WS; } } private boolean isEnSetCollinear(ArrayList<EastNorth> enAdjPts) { ArrayList<AlignWaysGeomPoint> awAdjPts = new ArrayList<>(); for (EastNorth en : enAdjPts) { AlignWaysGeomPoint pt = new AlignWaysGeomPoint(en.getX(), en.getY()); awAdjPts.add(pt); } return AlignWaysGeomPoint.isSetCollinear(awAdjPts); } private Node getNonEqualNode(WaySegment waySegment, Node endpoint) { if (waySegment.getFirstNode().equals(endpoint)) { return waySegment.getSecondNode(); } else if (waySegment.getSecondNode().equals(endpoint)) { return waySegment.getFirstNode(); } else return null; } /** * Reports invalid alignable statuses on screen in dialog boxes. * * @param stat The invalid status to report */ @Override void reportInvalidCommand(AlignableStatus stat) { String statMsg; switch (stat) { case ALGN_INV_CONNECTED_UNSHARED_PIVOT: statMsg = tr("Please select two segments that don''t share any nodes.\n" + "Alternatively put the pivot on their common node.\n"); break; case ALGN_INV_OUTSIDE_WORLD: statMsg = tr("Aligning would result nodes ''outside the world''.\n" + "Alignment not possible.\n"); break; case ALGN_INV_TOOMANY_CONNECTED_WS: statMsg = tr("There is at least a non-pivot endpoint of the alignee that joins more than two way segments.\n" + "Preserved angles type alignment is not possible.\n"); break; case ALGN_INV_ANGLE_PRESERVING_CONFLICT: statMsg = tr("The alignment is not possible with maintaining the angles of the joint segments.\n" + "Either choose the ''keep length'' aligning method or select other segments.\n"); break; case ALGN_INV_XPOINT_FALLSOUT: statMsg = tr("An intersection point would fall outside its adjacent nodes.\n" + "This is an unsupported scenario.\n"); break; default: statMsg = tr("Undocumented problem occured.\n"); break; } JOptionPane.showMessageDialog( Main.parent, tr(statMsg), tr("AlignWayS: Alignment not possible"), JOptionPane.WARNING_MESSAGE ); } /** * Returns true if the two selected segments are alignable. * They are not if they are connected *and* the pivot is not the connection * node. * They are also not if the moving segment endpoints connect to more than * one other way segment. */ @Override AlignableStatus areSegsAlignable() { AlignableStatus status = super.areSegsAlignable(); // The constructor may already have reported some problems: check. if (alignableStatKeepAngles != AlignableStatus.ALGN_VALID) return alignableStatKeepAngles; // The superclass may have reported problems: check. if (status != AlignableStatus.ALGN_VALID) return status; // Check the remainder of the potential problems: N/A // In all other cases alignment is possible return AlignableStatus.ALGN_VALID; } }