Changeset 18109 in josm for trunk


Ignore:
Timestamp:
2021-08-01T22:03:28+02:00 (3 years ago)
Author:
Don-vip
Message:

fix #21172 - Add method to create geometric distances from a point (patch by taylor.smock)

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/tools/Geometry.java

    r17141 r18109  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.tools;
     3
     4import static org.openstreetmap.josm.data.projection.Ellipsoid.WGS84;
    35
    46import java.awt.geom.Area;
     
    2628import org.openstreetmap.josm.data.coor.EastNorth;
    2729import org.openstreetmap.josm.data.coor.ILatLon;
     30import org.openstreetmap.josm.data.coor.LatLon;
    2831import org.openstreetmap.josm.data.osm.BBox;
    2932import org.openstreetmap.josm.data.osm.DataSet;
     
    15571560
    15581561    /**
     1562     * Create a new LatLon at a specified distance. Currently uses WGS84, but may change randomly in the future.
     1563     * This does not currently attempt to be hugely accurate. The actual location may be off
     1564     * depending upon the distance and the elevation, but should be within 0.0002 meters.
     1565     *
     1566     * @param original The originating point
     1567     * @param angle The angle (from true north) in radians
     1568     * @param offset The distance to the new point in the current projection's units
     1569     * @return The location at the specified angle and distance from the originating point
     1570     * @since 18109
     1571     */
     1572    public static ILatLon getLatLonFrom(final ILatLon original, final double angle, final double offset) {
     1573        final double meterOffset = ProjectionRegistry.getProjection().getMetersPerUnit() * offset;
     1574        final double radianLat = Math.toRadians(original.lat());
     1575        final double radianLon = Math.toRadians(original.lon());
     1576        final double angularDistance = meterOffset / WGS84.a;
     1577        final double lat = Math.asin(Math.sin(radianLat) * Math.cos(angularDistance)
     1578                + Math.cos(radianLat) * Math.sin(angularDistance) * Math.cos(angle));
     1579        final double lon = radianLon + Math.atan2(Math.sin(angle) * Math.sin(angularDistance) * Math.cos(radianLat),
     1580                Math.cos(angularDistance) - Math.sin(radianLat) * Math.sin(lat));
     1581        return new LatLon(Math.toDegrees(lat), Math.toDegrees(lon));
     1582    }
     1583
     1584    /**
    15591585     * Calculate closest distance between a line segment s1-s2 and a point p
    15601586     * @param s1 start of segment
  • trunk/test/unit/org/openstreetmap/josm/TestUtils.java

    r17372 r18109  
    2323import java.time.format.DateTimeFormatter;
    2424import java.time.temporal.Temporal;
     25import java.util.ArrayList;
    2526import java.util.Arrays;
    2627import java.util.Collection;
     
    3132import java.util.concurrent.ExecutionException;
    3233import java.util.concurrent.ThreadPoolExecutor;
     34import java.util.concurrent.atomic.AtomicInteger;
    3335import java.util.function.Function;
    3436import java.util.stream.Collectors;
     37import java.util.stream.IntStream;
    3538import java.util.stream.Stream;
    3639
     
    169172    }
    170173
     174    /**
     175     * Create a test matrix for parameterized tests.
     176     * <br />
     177     * <b>WARNING:</b> This can quickly become <i>very</i> large (this is combinatorial,
     178     * so the returned {@link Stream} length is the size of the object collections multiplied by each other.
     179     * So if you have three lists of size 3, 4, and 5, the stream size would be {@code 3 * 4 * 5} or 60 elements.
     180     * <br />
     181     * Generally speaking, you should avoid putting expected values into the test matrix.
     182     *
     183     * @param objectCollections The collections of objects. May include/provide {@code null}.
     184     * @return The object arrays to be used as arguments. Note: The returned stream might not be thread-safe.
     185     */
     186    public static Stream<Object[]> createTestMatrix(List<?>... objectCollections) {
     187        // Create the original object arrays
     188        final AtomicInteger size = new AtomicInteger(1);
     189        Stream.of(objectCollections).mapToInt(Collection::size).forEach(i -> size.set(size.get() * i));
     190        final List<Object[]> testMatrix = new ArrayList<>(size.get());
     191        final int[] indexes = IntStream.range(0, objectCollections.length).map(i -> 0).toArray();
     192
     193        // It is important to make a new object array each time (we modify them)
     194        return IntStream.range(0, size.get()).mapToObj(index -> new Object[objectCollections.length]).peek(args -> {
     195            // Just in case someone tries to make this parallel, synchronize on indexes to avoid most issues.
     196            synchronized (indexes) {
     197                // Set the args
     198                for (int listIndex = 0; listIndex < objectCollections.length; listIndex++) {
     199                    args[listIndex] = objectCollections[listIndex].get(indexes[listIndex]);
     200                }
     201                // Increment indexes
     202                for (int listIndex = 0; listIndex < objectCollections.length; listIndex++) {
     203                    indexes[listIndex] = indexes[listIndex] + 1;
     204                    if (indexes[listIndex] >= objectCollections[listIndex].size()) {
     205                        indexes[listIndex] = 0;
     206                    } else {
     207                        break;
     208                    }
     209                }
     210            }
     211        });
     212    }
     213
    171214    private static <T> String getFailMessage(T o1, T o2, int a, int b) {
    172215        return new StringBuilder("Compared\no1: ").append(o1).append("\no2: ")
  • trunk/test/unit/org/openstreetmap/josm/tools/GeometryTest.java

    r17275 r18109  
    1212import java.util.ArrayList;
    1313import java.util.Arrays;
     14import java.util.Collections;
    1415import java.util.List;
     16import java.util.stream.Stream;
    1517
    1618import org.junit.Assert;
     19import org.junit.jupiter.api.Test;
    1720import org.junit.jupiter.api.extension.RegisterExtension;
    18 import org.junit.jupiter.api.Test;
     21import org.junit.jupiter.params.ParameterizedTest;
     22import org.junit.jupiter.params.provider.Arguments;
     23import org.junit.jupiter.params.provider.MethodSource;
    1924import org.openstreetmap.josm.TestUtils;
    2025import org.openstreetmap.josm.data.coor.EastNorth;
     
    2732import org.openstreetmap.josm.data.osm.Way;
    2833import org.openstreetmap.josm.data.osm.search.SearchCompiler;
     34import org.openstreetmap.josm.data.projection.Projection;
     35import org.openstreetmap.josm.data.projection.ProjectionRegistry;
     36import org.openstreetmap.josm.data.projection.Projections;
    2937import org.openstreetmap.josm.io.OsmReader;
    3038import org.openstreetmap.josm.testutils.JOSMTestRules;
     
    470478    }
    471479
     480    static Stream<Arguments> testGetLatLonFrom() {
     481        // The projection can quickly explode the test matrix, so only test WGS84 (EPSG:3857). If other projections have
     482        // issues, add them to the first list.
     483        return TestUtils.createTestMatrix(
     484                // Check specific projections
     485                Collections.singletonList(Projections.getProjectionByCode("EPSG:3857")),
     486                // Check extreme latitudes (degrees)
     487                Arrays.asList(0, 89, -89),
     488                // Test extreme longitudes (degrees)
     489                Arrays.asList(0, -179, 179),
     490                // Test various angles (degrees)
     491                // This tests cardinal directions, and then some varying angles.
     492                // TBH, the cardinal directions should find any issues uncovered by the varying angles,
     493                // but it may not.
     494                Arrays.asList(0, 90, 180, 270, 45),
     495                // Test various distances (meters)
     496                Arrays.asList(1, 10_000)
     497                ).map(Arguments::of);
     498    }
     499
     500    @ParameterizedTest(name = "[{index}] {3}° {4}m @ lat = {1} lon = {2} - {0}")
     501    @MethodSource
     502    void testGetLatLonFrom(final Projection projection, final double lat, final double lon, final double angle, final double offsetInMeters) {
     503        ProjectionRegistry.setProjection(projection);
     504        final double offset = offsetInMeters / projection.getMetersPerUnit();
     505        final LatLon original = new LatLon(lat, lon);
     506
     507        final LatLon actual = (LatLon) Geometry.getLatLonFrom(original, Math.toRadians(angle), offset);
     508        // Due to degree -> radian -> degree conversion, there is a limit to how precise it can be
     509        assertEquals(offsetInMeters, original.greatCircleDistance(actual), 0.000_000_1);
     510        // The docs indicate that this should not be highly precise.
     511        assertEquals(angle, Math.toDegrees(original.bearing(actual)), 0.000_001);
     512    }
    472513}
Note: See TracChangeset for help on using the changeset viewer.