Skip to content

Instantly share code, notes, and snippets.

@marlonlom
Last active September 17, 2023 22:56
Show Gist options
  • Save marlonlom/f1be846eb07cdef831977773d846e7d0 to your computer and use it in GitHub Desktop.
Save marlonlom/f1be846eb07cdef831977773d846e7d0 to your computer and use it in GitHub Desktop.
LatLngConverter - Utility class for converting a coordinate into its DMS / Kotlin version
/*
* Copyright (c) 2023 Marlonlom
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package dev.marlonlom.libraries.laglongs
import kotlin.math.abs
/**
* Utility class for converting a coordinate into its DMS (degrees, minutes,
* seconds) representation.
*
* @author marlonlom
*/
object LatLngConverter {
/**
* Constant for latitude/longitude orientations.
*/
private val ORIENTATIONS: Array<String> = "N/S/E/W".split("/".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
/**
* Given a array of coordinates [longitude, latitude], returns the dms
* (degrees, minutes, seconds) representation.
*
* @param coordinates array of coordinates, with 2+ elements
* @return representation for given array
*/
fun processCoordinates(coordinates: FloatArray): String {
if (!isValidCoordinates(coordinates)) return ""
var converted0: String = decimalToDMS(coordinates[1])
val dmsLat = getLatitudeSignature(coordinates[1])
converted0 = "$converted0 $dmsLat".trim()
var converted1: String = decimalToDMS(coordinates[0])
val dmsLng = getLongitudeSignature(coordinates[0])
converted1 = "$converted1 $dmsLng".trim()
return "$converted0, $converted1"
}
/**
* Checks if coordinates in format [longitude, latitude] are valid.
*
* @param coordinates Coordinates array.
* @return true/false.
*/
private fun isValidCoordinates(
coordinates: FloatArray
): Boolean {
val isValidLatitude = isBetween(-90f, coordinates[1], 90f)
val isValidLongitude = isBetween(-180f, coordinates[0], 180f)
return isValidLatitude and isValidLongitude
}
/**
* Returns latitude signature for selected coordinates.
*
* @param coordinate coordinate value, with 2+ elements
* @return latitude signature, or empty if zero
*/
private fun getLatitudeSignature(coordinate: Float): String = when {
(isBetween(-90f, coordinate, 0f)) -> ORIENTATIONS[1]
(isBetween(0f, coordinate, 90f)) -> ORIENTATIONS[0]
else -> ""
}
/**
* Returns longitude signature for selected coordinates.
*
* @param coordinate coordinate value
* , with 2+ elements
* @return longitude signature, or empty if zero
*/
private fun getLongitudeSignature(coordinate: Float): String = when {
(isBetween(-180f, coordinate, 0f)) -> ORIENTATIONS[3]
(isBetween(0f, coordinate, 180f)) -> ORIENTATIONS[2]
else -> ""
}
/**
* Given a decimal longitudinal coordinate such as <i>-79.982195</i> it will
* be necessary to know whether it is a latitudinal or longitudinal
* coordinate in order to fully convert it.
*
* @param coordinates coordinate in decimal format
* @return coordinate in D°M′S″ format
* @see <a href='https://goo.gl/pWVp60'>Geographic coordinate conversion
* (wikipedia)</a>
*/
private fun decimalToDMS(coordinates: Float): String {
var coord = coordinates
var mod = coord % 1
var intPart = coord.toInt()
val degrees = intPart.toString()
coord = mod * 60
mod = coord % 1
intPart = coord.toInt()
if (intPart < 0) intPart *= -1
val minutes = intPart.toString()
coord = mod * 60
intPart = coord.toInt()
if (intPart < 0) intPart *= -1
val seconds = intPart.toString()
return abs(degrees.toInt()).toString() + "°" + minutes + "'" + seconds + "\""
}
/**
* Checks if provided float number is between two values (first, last)
*
* @param first Initial value for range.
* @param value Float number for range checks.
* @param last Final value for range.
*
* @return True/False if provided number is between first and last numbers.
*/
private fun isBetween(
first: Float, value: Float, last: Float
): Boolean = (value > first).and(value < last)
}
/*
* Copyright (c) 2023 Marlonlom
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package dev.marlonlom.libraries.laglongs
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
internal class LatLngConverterTest {
@Test
fun `Should success converting (-71,3854 , -30,8199) to DMS`() {
val expectedResult = "30°49'11\" S, 71°23'7\" W"
val coordinates = floatArrayOf(-71.3854f, -30.8199f)
val dmsResult = LatLngConverter.processCoordinates(coordinates)
println("dmsResult=$dmsResult")
assertNotNull(dmsResult)
assertEquals(expectedResult, dmsResult)
}
@Test
fun `Should success converting (-130,2988 , 50,7956) to DMS`() {
val expectedResult = "50°47'44\" N, 130°17'55\" W"
val coordinates = floatArrayOf(-130.2988f, 50.7956f)
val dmsResult = LatLngConverter.processCoordinates(coordinates)
println("dmsResult=$dmsResult")
assertNotNull(dmsResult)
assertEquals(expectedResult, dmsResult)
}
@Test
fun `Should success converting (127,8269 , -8,3135) to DMS`() {
val expectedResult = "8°18'48\" S, 127°49'36\" E"
val coordinates = floatArrayOf(127.8269f, -8.3135f)
val dmsResult = LatLngConverter.processCoordinates(coordinates)
println("dmsResult=$dmsResult")
assertNotNull(dmsResult)
assertEquals(expectedResult, dmsResult)
}
@Test
fun `Should success converting (90,6356 , 23,9566) to DMS`() {
val expectedResult = "23°57'23\" N, 90°38'8\" E"
val coordinates = floatArrayOf(90.6356f, 23.9566f)
val dmsResult = LatLngConverter.processCoordinates(coordinates)
println("dmsResult=$dmsResult")
assertNotNull(dmsResult)
assertEquals(expectedResult, dmsResult)
}
@Test
fun `Should success converting (0,0) to DMS`() {
val expectedResult = "0°0'0\", 0°0'0\""
val coordinates = floatArrayOf(0f, 0f)
val dmsResult = LatLngConverter.processCoordinates(coordinates)
println("dmsResult=$dmsResult")
assertNotNull(dmsResult)
assertEquals(expectedResult, dmsResult)
}
@Test
fun `Should success converting (180, 180) to DMS`() {
val coordinates = floatArrayOf(180f, -181f)
val dmsResult = LatLngConverter.processCoordinates(coordinates)
println("dmsResult=$dmsResult")
assertNotNull(dmsResult)
assertTrue(dmsResult.isEmpty())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment