Skip to content

Commit

Permalink
Add distance between two places
Browse files Browse the repository at this point in the history
  • Loading branch information
dacr committed Jul 24, 2024
1 parent a502767 commit 5bb830e
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ object DecimalDegrees {
}

extension (dd: LatitudeDecimalDegrees) {
@targetName("doubleValue_latitude")
def doubleValue: Double = dd
def toRadians: Double = dd.toRadians
}
extension (dd: LongitudeDecimalDegrees) {
@targetName("doubleValue_longitude")
def doubleValue: Double = dd
@targetName("doubleValue_toRadians")
def toRadians: Double = dd.toRadians
}

}
Expand Down Expand Up @@ -106,15 +108,15 @@ case class PhotoPlace(
latitude: LatitudeDecimalDegrees,
longitude: LongitudeDecimalDegrees,
altitude: Option[AltitudeMeanSeaLevel],
deducted: Boolean
deducted: Boolean
)

object PhotoPlace {
def apply(
latitudeDMS: LatitudeDegreeMinuteSeconds,
longitudeDMS: LongitudeDegreeMinuteSeconds,
altitudeMeanSeaLevel: Option[AltitudeMeanSeaLevel],
deducted: Boolean
altitudeMeanSeaLevel: Option[AltitudeMeanSeaLevel] = None,
deducted: Boolean = false
): PhotoPlace = {
PhotoPlace(
latitudeDMS.toDecimalDegrees,
Expand All @@ -123,4 +125,54 @@ object PhotoPlace {
deducted
)
}

def fromDecimalDegrees(
latitudeDMS: LatitudeDecimalDegrees,
longitudeDMS: LongitudeDecimalDegrees,
altitudeMeanSeaLevel: Option[AltitudeMeanSeaLevel] = None,
deducted: Boolean = false
): PhotoPlace = {
PhotoPlace(
latitudeDMS,
longitudeDMS,
altitudeMeanSeaLevel,
deducted
)
}

def fromLocationSpecs(
latitudeSpec: String,
longitudeSpec: String,
altitudeMeanSeaLevel: Option[AltitudeMeanSeaLevel] = None,
deducted: Boolean = false
): Try[PhotoPlace] = {
for {
latitudeDMS <- LatitudeDegreeMinuteSeconds.fromSpec(latitudeSpec)
longitudeDMS <- LongitudeDegreeMinuteSeconds.fromSpec(longitudeSpec)
} yield PhotoPlace(
latitudeDMS,
longitudeDMS,
altitudeMeanSeaLevel,
deducted
)
}

extension (from: PhotoPlace) {
def distanceTo(to: PhotoPlace): Double = {
val earthRadius = 6371000d
val deltaLatitude = to.latitude.toRadians - from.latitude.toRadians
val deltaLongitude = to.longitude.toRadians - from.longitude.toRadians

val a =
Math.sin(deltaLatitude / 2) * Math.sin(deltaLatitude / 2) +
Math.sin(deltaLongitude / 2) * Math.sin(deltaLongitude / 2) *
Math.cos(from.latitude.toRadians) *
Math.cos(to.latitude.toRadians)

val c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

earthRadius * c
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import zio.*
import zio.ZIO.*
import zio.test.*
import fr.janalyse.sotohp.model.DegreeMinuteSeconds.*
import fr.janalyse.sotohp.model.DecimalDegrees.*
import fr.janalyse.sotohp.model.DecimalDegrees.{LatitudeDecimalDegrees, *}

import scala.util.{Success, Try}

Expand All @@ -19,7 +19,7 @@ object PhotoPlaceSpec extends ZIOSpecDefault {
TestDataSet("from wikipedia decimal places 5 case", "0° 00′ 0.036″ N", 9.999999999999999e-6),
TestDataSet("alternative representation 1", "3°58'24\" S", -3.9733333333333336d),
TestDataSet("alternative representation 2", "03°58'24\" S", -3.9733333333333336d),
//TestDataSet("alternative representation 3", "-3°58'24\" S", -3.9733333333333336d), // TODO Check the meaning of negative values in DMS part
// TestDataSet("alternative representation 3", "-3°58'24\" S", -3.9733333333333336d), // TODO Check the meaning of negative values in DMS part
TestDataSet("alternative representation 4", "3° 58' 24\" S", -3.9733333333333336d),
TestDataSet("alternative representation 5", "3° 58' 24'' S", -3.9733333333333336d),
TestDataSet("alternative representation 6", "3° 58' 24″ S", -3.9733333333333336d),
Expand All @@ -34,28 +34,55 @@ object PhotoPlaceSpec extends ZIOSpecDefault {
)

override def spec =
suite("Degrees minutes seconds")(
suite("for latitude")(
for {
TestDataSet(testName, givenDMSSpec, expectedDegrees) <- latitudeTestDataset
} yield test(testName)(
suite("PhotoPlace Test Suite")(
suite("DegreesMinutesSeconds features")(
suite("should support various encoding for latitude")(
for {
dms <- from(LatitudeDegreeMinuteSeconds.fromSpec(givenDMSSpec))
} yield assertTrue(
dms.toDecimalDegrees == LatitudeDecimalDegrees(expectedDegrees)
TestDataSet(testName, givenDMSSpec, expectedDegrees) <- latitudeTestDataset
} yield test(testName)(
for {
dms <- from(LatitudeDegreeMinuteSeconds.fromSpec(givenDMSSpec))
} yield assertTrue(
dms.toDecimalDegrees == LatitudeDecimalDegrees(expectedDegrees)
)
)
)
),
suite("for longitude")(
for {
TestDataSet(testName, givenDMSSpec, expectedDegrees) <- longitudeTestDataSet
} yield test(testName)(
),
suite("should support various encoding for latitude")(
for {
dms <- from(LongitudeDegreeMinuteSeconds.fromSpec(givenDMSSpec))
} yield assertTrue(
dms.toDecimalDegrees == LongitudeDecimalDegrees(expectedDegrees)
TestDataSet(testName, givenDMSSpec, expectedDegrees) <- longitudeTestDataSet
} yield test(testName)(
for {
dms <- from(LongitudeDegreeMinuteSeconds.fromSpec(givenDMSSpec))
} yield assertTrue(
dms.toDecimalDegrees == LongitudeDecimalDegrees(expectedDegrees)
)
)
)
),
suite("Distance features")(
test("should return zero when the same place is given") {
val from = PhotoPlace.fromDecimalDegrees(LatitudeDecimalDegrees(0d), LongitudeDecimalDegrees(0d))
val to = from
assertTrue(
from.distanceTo(to) == 0
)
},
test("should return the right distance between two places") {
ZIO.fromTry(
for {
paris <- PhotoPlace.fromLocationSpecs("48° 51' 52.9776'' N", "2° 20' 56.4504'' E")
brest <- PhotoPlace.fromLocationSpecs("48° 23' 23.9964'' N", "4° 29' 24.0000'' W")
dist1 = paris.distanceTo(brest)
dist2 = brest.distanceTo(paris)
} yield {
assertTrue(
dist1 == dist2,
dist1 < 506_000,
dist1 > 504_000
)
}
)
}
)
)
}

0 comments on commit 5bb830e

Please sign in to comment.