001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.validation.tests;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.util.Collections;
007import java.util.List;
008
009import org.openstreetmap.josm.actions.OrthogonalizeAction;
010import org.openstreetmap.josm.actions.OrthogonalizeAction.InvalidUserInputException;
011import org.openstreetmap.josm.data.osm.Node;
012import org.openstreetmap.josm.data.osm.Way;
013import org.openstreetmap.josm.data.validation.Severity;
014import org.openstreetmap.josm.data.validation.Test;
015import org.openstreetmap.josm.data.validation.TestError;
016import org.openstreetmap.josm.gui.progress.ProgressMonitor;
017import org.openstreetmap.josm.spi.preferences.Config;
018import org.openstreetmap.josm.tools.Logging;
019import org.openstreetmap.josm.tools.Pair;
020
021/**
022 * Checks for buildings with angles close to right angle.
023 *
024 * @author marxin
025 * @since 13670
026 */
027public class RightAngleBuildingTest extends Test {
028
029    /** Maximum angle difference from right angle that is considered as invalid. */
030    protected double maxAngleDelta;
031
032    /** Minimum angle difference from right angle that is considered as invalid. */
033    protected double minAngleDelta;
034
035    /**
036     * Constructs a new {@code RightAngleBuildingTest} test.
037     */
038    public RightAngleBuildingTest() {
039        super(tr("Almost right angle buildings"),
040                tr("Checks for buildings that have angles close to right angle and are not orthogonalized."));
041    }
042
043    @Override
044    public void visit(Way w) {
045        if (!w.isUsable() || !w.isClosed() || !isBuilding(w)) return;
046
047        List<Pair<Double, Node>> angles = w.getAngles();
048        for (Pair<Double, Node> pair: angles) {
049            if (checkAngle(pair.a)) {
050                TestError.Builder builder = TestError.builder(this, Severity.WARNING, 3701)
051                                                     .message(tr("Building with an almost square angle"))
052                                                     .primitives(w)
053                                                     .highlight(pair.b);
054                if (angles.stream().noneMatch(
055                        p -> Math.abs(p.a - 90) >= maxAngleDelta && Math.abs(p.a - 180) >= minAngleDelta)) {
056                    builder.fix(() -> {
057                        try {
058                            return OrthogonalizeAction.orthogonalize(Collections.singleton(w));
059                        } catch (InvalidUserInputException e) {
060                            Logging.warn(e);
061                            return null;
062                        }
063                    });
064                }
065                errors.add(builder.build());
066                return;
067            }
068        }
069    }
070
071    @Override
072    public void startTest(ProgressMonitor monitor) {
073        super.startTest(monitor);
074        maxAngleDelta = Config.getPref().getDouble("validator.RightAngleBuilding.maximumDelta", 10.0);
075        minAngleDelta = Config.getPref().getDouble("validator.RightAngleBuilding.minimumDelta", 0.25);
076    }
077
078    private boolean checkAngle(double angle) {
079        double difference = Math.abs(angle - 90);
080        return difference > minAngleDelta && difference < maxAngleDelta;
081    }
082}