Modify

Opened 3 years ago

Last modified 3 years ago

#22460 new enhancement

After squaring a building and uploading, the downloaded building is not square enough for the extrude tool to avoid creating unnecessary nodes

Reported by: bo.goiny@… Owned by: team
Priority: normal Milestone:
Component: Core Version:
Keywords: extrusion square node shift rounding Cc:

Description

i created another ticket before, wich was not specific enough. here is the link to the discussion there
https://josm.openstreetmap.de/ticket/22454

i also did some more test with some features i created near equator with the buildingstool. after up- and downloading again i used extrusion tool. the smaller the buildings are, i think the more frequently extrusion tool should fail because angles getting bigger due to the shift of nodes.

the non orthogonality of the smallest feature after up and download is even visible with the bare eye

extrusion fails already with bigger buildings at realistic building sizes.

Attachments (3)

01_nodeshift_test_before_upload.geojson (8.0 KB ) - added by bo.goiny@… 3 years ago.
features with 11 digits for decimal portion directly saved from JOSM
02_nodeshift_test_after_download.geojson (8.0 KB ) - added by anonymous 3 years ago.
the same features up and downloaded from OSM Server with only 7 digits for decimal portion of coordinates
03_extrusion_fails_on_downloaded_features.geojson (9.5 KB ) - added by bo.goiny@… 3 years ago.
use of extrusion tool fails on features because they are not square enough

Download all attachments as: .zip

Change History (6)

by bo.goiny@…, 3 years ago

features with 11 digits for decimal portion directly saved from JOSM

by anonymous, 3 years ago

the same features up and downloaded from OSM Server with only 7 digits for decimal portion of coordinates

by bo.goiny@…, 3 years ago

use of extrusion tool fails on features because they are not square enough

comment:1 by taylor.smock, 3 years ago

I've been debugging this, and it looks like it is due to (a) using EastNorth and (b) not correcting for ILatLon.MAX_SERVER_PRECISION. There is a third issue where the math to check for a zero angle does not account for cases where the first and third locations could be switched and the angle could be < 1e-5.

The fix I'm looking at for this is probably going to have to wait for #22453 (for the Geometry#segmentsParallel fix) to avoid accidentally redoing the same work multiple times.

Anyway, minimal patch for the zero angle check fix:

  • src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java

    diff --git a/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java b/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java
    a b  
    3838import org.openstreetmap.josm.data.UndoRedoHandler;
    3939import org.openstreetmap.josm.data.coor.EastNorth;
    4040import org.openstreetmap.josm.data.coor.ILatLon;
     41import org.openstreetmap.josm.data.coor.LatLon;
    4142import org.openstreetmap.josm.data.osm.DataIntegrityProblemException;
    4243import org.openstreetmap.josm.data.osm.DataSet;
     44import org.openstreetmap.josm.data.osm.INode;
    4345import org.openstreetmap.josm.data.osm.Node;
    4446import org.openstreetmap.josm.data.osm.OsmPrimitive;
    4547import org.openstreetmap.josm.data.osm.Way;
     
    651653        boolean nodeOverlapsSegment = prevNode != null && Geometry.segmentsParallel(initialN1en, prevNode.getEastNorth(), initialN1en, newN1en);
    652654        // segmentAngleZero marks subset of nodeOverlapsSegment.
    653655        // nodeOverlapsSegment is true if angle between segments is 0 or PI, segmentAngleZero only if angle is 0
    654         boolean segmentAngleZero = prevNode != null && Math.abs(Geometry.getCornerAngle(prevNode.getEastNorth(), initialN1en, newN1en)) < 1e-5;
     656        boolean segmentAngleZero = checkAngleIsZero(prevNode, initialN1en, newN1en);
    655657        boolean hasOtherWays = hasNodeOtherWays(selectedSegment.getFirstNode(), selectedSegment.getWay());
    656658        List<Node> changedNodes = new ArrayList<>();
    657659        if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) {
     
    681683        //find if the new points overlap existing segments (in case of 90 degree angles)
    682684        Node nextNode = getNextNode(selectedSegment.getUpperIndex());
    683685        nodeOverlapsSegment = nextNode != null && Geometry.segmentsParallel(initialN2en, nextNode.getEastNorth(), initialN2en, newN2en);
    684         segmentAngleZero = nextNode != null && Math.abs(Geometry.getCornerAngle(nextNode.getEastNorth(), initialN2en, newN2en)) < 1e-5;
     686        segmentAngleZero = checkAngleIsZero(nextNode, initialN2en, newN2en);
    685687        hasOtherWays = hasNodeOtherWays(selectedSegment.getSecondNode(), selectedSegment.getWay());
    686688
    687689        if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) {
     
    722724        joinNodesIfCollapsed(changedNodes);
    723725    }
    724726
     727    /**
     728     * Check if an angle is almost zero, taking into account server precision
     729     * @param node The node to check
     730     * @param segmentNode1 A node of the segment to check
     731     * @param segmentNode2 A node of the segment to check
     732     * @return {@code true} if it is likely that the angle is supposed to be zero.
     733     */
     734    private boolean checkAngleIsZero(INode node, EastNorth segmentNode1, EastNorth segmentNode2) {
     735        if (node != null) {
     736            // Convert into LatLon -- we need distance from the line in degrees to compare against server precision
     737            // This bit should probably be in Geometry.
     738            LatLon ll1 = ProjectionRegistry.getProjection().eastNorth2latlon(segmentNode1);
     739            LatLon ll2 = ProjectionRegistry.getProjection().eastNorth2latlon(segmentNode2);
     740            double x0 = node.lon(); double x1 = ll1.lon(); double x2 = ll2.lon();
     741            double y0 = node.lat(); double y1 = ll1.lat(); double y2 = ll2.lat();
     742            double distanceToNewSegment = Math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) /
     743                    Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
     744            double angle = Math.abs(Geometry.getCornerAngle(node.getEastNorth(), segmentNode1, segmentNode2));
     745            // Account for cases where the first/third points should have been reversed
     746            if (angle > Math.PI / 2) {
     747                angle = Math.abs(angle - Math.PI);
     748            }
     749            // 1e-5 radians == 0.0057 degrees
     750            // 1e-2 radians == 0.57 degrees
     751            return angle < 1e-5 ||
     752                    (angle < 1e-2 && distanceToNewSegment < ILatLon.MAX_SERVER_PRECISION);
     753        }
     754        return false;
     755    }
     756
    725757    private void joinNodesIfCollapsed(List<Node> changedNodes) {
    726758        if (!dualAlignActive || newN1en == null || newN2en == null) return;
    727759        if (newN1en.distance(newN2en) > 1e-6) return;

comment:2 by bo.goiny@…, 3 years ago

thank you very much for your answer!

is it possible to say at wich angle difference to 90° extrusion tool starts to fail?
sorry, i only know a little bit of python, im not 100% understanding the java code. do i understand it right, that extrude works savely for all angles +- 90.0057 degrees?

comment:3 by taylor.smock, 3 years ago

It currently starts to fail at 0.0057 degrees. Rather unfortunately, the calculation for that angle (Geometry.getCornerAngle) treats the calculation as if the right side of the line formed by a, b, c (node.getEastNorth(), segmentNode1, segmentNode2) as the "interior" of the corner, and returns that. This is the correct behavior for the specification, but for the purposes of this calculation, I believe we want to have the smaller of the two angles.

Anyway, keep in mind that I'm reading the code for the extrude tool for the first time, so I'm having to figure out what it is supposed to do and why. I'm much more familiar with other parts of the code base.

Modify Ticket

Change Properties
Set your email in Preferences
Action
as new The owner will remain team.
as The resolution will be set. Next status will be 'closed'.
to The owner will be changed from team to the specified user.
Next status will be 'needinfo'. The owner will be changed from team to bo.goiny@….
as duplicate The resolution will be set to duplicate. Next status will be 'closed'. The specified ticket will be cross-referenced with this ticket.
The owner will be changed from team to anonymous. Next status will be 'assigned'.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.