Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add spatial types #9

Closed
jamesdh opened this issue Nov 16, 2023 · 6 comments · Fixed by #12
Closed

Add spatial types #9

jamesdh opened this issue Nov 16, 2023 · 6 comments · Fixed by #12
Assignees
Labels
enhancement New feature or request

Comments

@jamesdh
Copy link

jamesdh commented Nov 16, 2023

Specifically thinking latitude/longitude in decimal degrees vs degrees/minutes/seconds.

@jamesdh
Copy link
Author

jamesdh commented Nov 16, 2023

Could possibly tie this in with Distance measurements via a simple Haversine calculation

@pjazdzyk
Copy link
Owner

Hi James, thanks for an interesting suggestion! Let me look into this - I will let you know if I manage to add this.

@pjazdzyk pjazdzyk added the enhancement New feature or request label Nov 19, 2023
@pjazdzyk pjazdzyk self-assigned this Dec 16, 2023
@pjazdzyk
Copy link
Owner

pjazdzyk commented Dec 29, 2023

Hi @jamesdh ,

It took me a while, but it was bit more complicated then I originally assumed. But the feature is ready, you can test it if you can spare a minute and let me know if this is what you expected. I have created plenty of unit tests for it to make sure it works, including some manual tests with Google Maps, it seems to work OK.

Branch:
https://github.com/pjazdzyk/unitility/tree/feature/SNSUNI-69_geo_distance_using_harvesine_equations

        // Latitude and Longitude types are based on Angular units
        Latitude latitude = Latitude.ofDegrees(-20.123);
        Longitude longitude = Longitude.ofDegrees(20.123);
        // They can be reduced to a string in DMS format or in ENG format:
        String latInDMS = latitude.toDMSFormat(2);                 // Outputs: 20°7'22.8"S
        String latInENG = latitude.toEngineeringFormat();         // Outputs: -20.123 [°]

        // Instance from degrees, minutes, seconds
        Latitude latFromDMS = Latitude.ofDegMinSec(20, 7, 22.8, CardinalDirection.SOUTH);   // Latitude{-20.123°}
        Longitude longFromDMS = Longitude.ofDegMinSec(20, 7, 22.8, CardinalDirection.EAST); // Longitude{20.123°}

        // GeoCoordinate class represents a coordinate of specific point on the globe, using Latitude and Longitude
        GeoCoordinate coordinateExample = GeoCoordinate.of(latitude, longitude, "my location");
        // GeoCoordinate can be reduced to DMS format, ENG format, or decimal degrees format
        // Decimal degrees format with coma separating latitude from longitude is for ie: how Google Maps output cords
        String geoCoordDMS = coordinateExample.toDMSFormat(2);             // 20°7'22.8"S, 20°7'22.8"E
        String geoCoordEND = coordinateExample.toEngineeringFormat(2);     // -20.12 [°], 20.12 [°]
        String geoCoordDEC = coordinateExample.toDecimalDegrees(2);        // -20.12, 20.12

        // GeoDistance represents spherical distance between two coordinates on Earth
        // Distance is calculated based on Haversine equations. It outputs curved distance using Distance instance
        // and true bearing withing provided coordinates in range <-180,+180> as Angle instance.
        GeoCoordinate wroclaw = GeoCoordinate.of(
                Latitude.ofDegrees(51.102772),
                Longitude.ofDegrees(16.885802),
                "Wroclaw");
        GeoCoordinate newYork = GeoCoordinate.of(
                Latitude.ofDegrees(40.712671),
                Longitude.ofDegrees(-74.004655),
                "NewYork");

        GeoDistance geoDistance = GeoDistance.ofKilometers(wroclaw, newYork);
        String distanceInEng = geoDistance.toEngineeringFormat();   // 6669.896095258197 [km]
        Angle trueBearing = geoDistance.getTrueBearing();           // Angle{-61.07915625042435°}

        // Geo distance can be specified by providing true bearing and distance from the starting coordinate.
        // Target coordinate will be calculated accordingly:
        GeoDistance toNewYork = GeoDistance.of(wroclaw, trueBearing, Distance.ofKilometers(6669.896095258197));
        String targetCoordinate = toNewYork.getTargetCoordinate().toDecimalDegrees();   // 40.712671, -74.004655

        // From created GeoDistance instance we can travel further using two methods: with() and translate().
        // a) with() will retain your starting point, create new instance and calculate new distance
        GeoCoordinate wellington = GeoCoordinate.of(
                Latitude.ofDegrees(-41.289463),
                Longitude.ofDegrees(174.774913),
                "Wellington");

        GeoDistance toWellington = toNewYork.with(wellington);
        Distance distance = toWellington.getDistance();     // 18005.6198226 km
        String wroclawCoordinate = toWellington.getStartCoordinate().toDecimalDegrees();     // 51.102772, 16.885802
        String wellingtonCoordinate = toWellington.getTargetCoordinate().toDecimalDegrees(); // -41.289463, 174.774913

        // b) translate() will take your current target coordinate as start coordinate, create new instance and set 
        // new target based on argument input
        GeoDistance toWellingtonBis = toNewYork.translate(wellington);
        Distance distanceBis = toWellingtonBis.getDistance();     // 14403.6934729 km
        String wroclawCoordinateBis = toWellingtonBis.getStartCoordinate().toDecimalDegrees();     // 40.712671, -74.004655
        String wellingtonCoordinateBis = toWellingtonBis.getTargetCoordinate().toDecimalDegrees(); // -41.289463, 174.774913
        // Both methods also allow specifying bearing and distance instead of target coordinate

        // String parsers:
        // Proper deserializers are defined for Spring and Quarkus for proper JSON deserialization / serialization.
        // Parsing factories also can be used in code directly:
        GeoQuantityParsingFactory geoParsingFactory = PhysicalQuantityParsingFactory.GEO_PARSING_FACTORY;
        Latitude parsedLat1 = geoParsingFactory.parseFromDMSFormat(Latitude.class, "20°7'22.8\"S");
        Latitude parsedLat2 = geoParsingFactory.parseFromDMSFormat(Latitude.class, "20deg 7min 22.8sec");

@pjazdzyk pjazdzyk pinned this issue Dec 29, 2023
@pjazdzyk
Copy link
Owner

pjazdzyk commented Dec 30, 2023

I have just fixed the arithmetic operations, they now will work as expected and allow for adding quantities to each other, of different types but sharing the same unit type. GeoDistance when other distance or value is added, will assume the same bearing, add new distance to current and recalculate new target coordinate, retaining its starting point.

// Arithmetic operations
// Sum of two GeoQuantities
GeoCoordinate start = GeoCoordinate.of(Latitude.ofDegrees(51.1), Longitude.ofDegrees(16.9));
GeoCoordinate target = GeoCoordinate.of(Latitude.ofDegrees(40.8), Longitude.ofDegrees(-74.1));

GeoDistance firstGeoDistance = GeoDistance.ofKilometers(start, target);         // 6670.048729447209 km
GeoDistance secondGeoDistance = firstGeoDistance.translate(Distance.ofKilometers(1000)); // 1000 km

GeoDistance sumOfDistances = firstGeoDistance.plus(secondGeoDistance);          // 7670.048729447209 km and new target
GeoDistance greaterDistance = sumOfDistances.plus(Distance.ofKilometers(1000)); // 8670.048729447209 km and new target
GeoDistance evenGreaterDistance = greaterDistance.plus(1000);                   // 9670.048729447209 km and new target

@pjazdzyk pjazdzyk linked a pull request Jan 2, 2024 that will close this issue
@pjazdzyk
Copy link
Owner

pjazdzyk commented Jan 2, 2024

Included in release: 2.1.1

@jamesdh
Copy link
Author

jamesdh commented Mar 12, 2024

@pjazdzyk you are awesome! I'll haven't had a chance to test this yet, but I'll be coming back around to the project that I originally used unitility with soon. Looking forward to giving this a go!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants