Amenities GeoUtils

This commit is contained in:
Dimitris
2025-12-17 13:07:29 +01:00
parent ed24e71473
commit b9030dbc50
10 changed files with 198 additions and 197 deletions

View File

@@ -67,26 +67,6 @@ data class StepData (
)
// GeoJSON data classes
@Serializable
data class GeoJsonType(
val type: String,
val coordinates: List<List<Double>>
)
@Serializable
data class GeoJsonFeature(
val type: String,
val geometry: GeoJsonType,
)
@Serializable
data class GeoJsonFeatureCollection(
val type: String,
val features: List<GeoJsonFeature>
)
@Serializable
data class Locations (
var lat : Double,

View File

@@ -6,14 +6,11 @@ import com.kouros.navigation.data.valhalla.Maneuvers
import com.kouros.navigation.data.valhalla.Summary
import com.kouros.navigation.data.valhalla.Trip
import com.kouros.navigation.data.valhalla.ValhallaJson
import com.kouros.navigation.utils.NavigationUtils.createCenterLocation
import com.kouros.navigation.utils.NavigationUtils.createGeoJson
import com.kouros.navigation.utils.NavigationUtils.decodePolyline
import com.kouros.navigation.utils.GeoUtils.createCenterLocation
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
import com.kouros.navigation.utils.GeoUtils.decodePolyline
import com.kouros.navigation.utils.location
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.Point
import org.maplibre.turf.TurfMeasurement
import kotlin.math.roundToInt
data class Route(
@@ -95,7 +92,7 @@ data class Route(
points.add(point)
}
pointLocations = points
routeGeoJson = createGeoJson("LineString", waypoints)
routeGeoJson = createLineStringCollection( waypoints)
centerLocation = createCenterLocation(routeGeoJson)
return Route(
maneuvers,

View File

@@ -2,6 +2,8 @@ package com.kouros.navigation.data.overpass
import android.location.Location
import com.google.gson.GsonBuilder
import com.kouros.navigation.utils.GeoUtils.getBoundingBox2
import com.kouros.navigation.utils.GeoUtils.getOverpassBbox
import com.kouros.navigation.utils.NavigationUtils
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
@@ -11,8 +13,8 @@ class Overpass {
val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
fun getAmenities(category: String, location: Location) : List<Elements> {
val boundingBox = NavigationUtils.getOverpassBbox(location, 2.0)
val bb = NavigationUtils.getBoundingBox2(location, 2.0)
val boundingBox = getOverpassBbox(location, 2.0)
val bb = getBoundingBox2(location, 2.0)
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
httpURLConnection.requestMethod = "POST"
httpURLConnection.setRequestProperty(

View File

@@ -0,0 +1,153 @@
package com.kouros.navigation.utils
import android.location.Location
import com.kouros.navigation.data.BoundingBox
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.Point
import org.maplibre.spatialk.geojson.Feature
import org.maplibre.spatialk.geojson.dsl.addFeature
import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection
import org.maplibre.spatialk.geojson.dsl.buildLineString
import org.maplibre.spatialk.geojson.toJson
import org.maplibre.turf.TurfMeasurement
import org.maplibre.turf.TurfMisc
import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import kotlin.math.cos
import kotlin.math.pow
object GeoUtils {
fun snapLocation(location: Location, stepCoordinates: List<Point>) : Location {
val newLocation = Location(location)
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
if (stepCoordinates.size > 1) {
val pointFeature = TurfMisc.nearestPointOnLine(oldPoint, stepCoordinates)
val point = pointFeature.geometry() as Point
newLocation.latitude = point.latitude()
newLocation.longitude = point.longitude()
}
return newLocation
}
fun decodePolyline(encoded: String, vararg precisionOptional: Int): List<List<Double>> {
val precision = if (precisionOptional.isNotEmpty()) precisionOptional[0] else 6
val factor = 10.0.pow(precision)
var lat = 0
var lng = 0
val coordinates = mutableListOf<List<Double>>()
var index = 0
while (index < encoded.length) {
var byte = 0x20
var shift = 0
var result = 0
while (byte >= 0x20) {
byte = encoded[index].code - 63
result = result or ((byte and 0x1f) shl shift)
shift += 5
index++
}
lat += if ((result and 1) > 0) (result shr 1).inv() else (result shr 1)
byte = 0x20
shift = 0
result = 0
while (byte >= 0x20) {
byte = encoded[index].code - 63
result = result or ((byte and 0x1f) shl shift)
shift += 5
index++
}
lng += if ((result and 1) > 0) (result shr 1).inv() else (result shr 1)
coordinates.add(listOf(lng.toDouble() / factor, lat.toDouble() / factor))
}
return coordinates
}
/**
* Calculates the geographic center of the route's GeoJSON data.
*
* @return A [Location] object representing the center point.
* @throws IllegalStateException if the calculated center does not have valid Point geometry.
*/
fun createCenterLocation(routeGeoJson: String): Location {
// 1. Create a FeatureCollection from the raw GeoJSON string.
val featureCollection = FeatureCollection.fromJson(routeGeoJson)
// 2. Calculate the center feature of the collection.
val centerFeature = TurfMeasurement.center(featureCollection)
// 3. Safely access and cast the geometry, throwing an informative error if it fails.
val centerPoint = centerFeature.geometry() as? Point
?: throw IllegalStateException("Center of GeoJSON is not a valid Point.")
// 4. Create and return the Location object.
return location(centerPoint.longitude(), centerPoint.latitude())
}
fun createLineStringCollection(lineCoordinates: List<List<Double>>): String {
val lineString = buildLineString {
lineCoordinates.forEach {
add(org.maplibre.spatialk.geojson.Point(
it[0],
it[1]
))
}
}
val feature = Feature(lineString, null)
val featureCollection = org.maplibre.spatialk.geojson.FeatureCollection(feature)
return featureCollection.toJson()
}
fun createPointCollection(lineCoordinates: List<List<Double>>): String {
val featureCollection = buildFeatureCollection {
lineCoordinates.forEach {
addFeature {
geometry = org.maplibre.spatialk.geojson.Point(it[0], it[1])
properties = null
}
}
}
return featureCollection.toJson()
}
fun getOverpassBbox(location: Location, radius: Double): String {
val bbox = getBoundingBox(location.longitude, location.latitude, radius)
val neLon = bbox["ne"]?.get("lon")
val neLat = bbox["ne"]?.get("lat")
val swLon = bbox["sw"]?.get("lon")
val swLat = bbox["sw"]?.get("lat")
return "$swLon,$swLat,$neLon,$neLat"
}
fun getBoundingBox2(location: Location, radius: Double): BoundingBox {
val bbox = getBoundingBox(location.longitude, location.latitude, radius)
val neLon = bbox["ne"]?.get("lon")
val neLat = bbox["ne"]?.get("lat")
val swLon = bbox["sw"]?.get("lon")
val swLat = bbox["sw"]?.get("lat")
return BoundingBox(swLat ?: 0.0 , swLon ?: 0.0, neLat ?: 0.0, neLon ?: 0.0)
}
fun getBoundingBox(
lat: Double,
lon: Double,
radius: Double
): Map<String, Map<String, Double>> {
val earthRadius = 6371.0
val maxLat = lat + toDegrees(radius / earthRadius)
val minLat = lat - toDegrees(radius / earthRadius)
val maxLon = lon + toDegrees(radius / earthRadius / cos(toRadians(lat)))
val minLon = lon - toDegrees(radius / earthRadius / cos(toRadians(lat)))
return mapOf(
"nw" to mapOf("lat" to maxLat, "lon" to minLon),
"ne" to mapOf("lat" to maxLat, "lon" to maxLon),
"sw" to mapOf("lat" to minLat, "lon" to minLon),
"se" to mapOf("lat" to minLat, "lon" to maxLon)
)
}
}

View File

@@ -6,12 +6,13 @@ import android.location.LocationManager
import androidx.core.content.edit
import com.kouros.navigation.data.BoundingBox
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
import com.kouros.navigation.data.GeoJsonFeature
import com.kouros.navigation.data.GeoJsonFeatureCollection
import com.kouros.navigation.data.GeoJsonType
import kotlinx.serialization.json.Json
import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.Point
import org.maplibre.spatialk.geojson.Feature
import org.maplibre.spatialk.geojson.dsl.addFeature
import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection
import org.maplibre.spatialk.geojson.dsl.buildLineString
import org.maplibre.spatialk.geojson.toJson
import org.maplibre.turf.TurfMeasurement
import org.maplibre.turf.TurfMisc
import java.lang.Math.toDegrees
@@ -77,121 +78,6 @@ object NavigationUtils {
apply()
}
}
fun snapLocation(location: Location, stepCoordinates: List<Point>) : Location {
val newLocation = Location(location)
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
if (stepCoordinates.size > 1) {
val pointFeature = TurfMisc.nearestPointOnLine(oldPoint, stepCoordinates)
val point = pointFeature.geometry() as Point
newLocation.latitude = point.latitude()
newLocation.longitude = point.longitude()
}
return newLocation
}
fun decodePolyline(encoded: String, vararg precisionOptional: Int): List<List<Double>> {
val precision = if (precisionOptional.isNotEmpty()) precisionOptional[0] else 6
val factor = 10.0.pow(precision)
var lat = 0
var lng = 0
val coordinates = mutableListOf<List<Double>>()
var index = 0
while (index < encoded.length) {
var byte = 0x20
var shift = 0
var result = 0
while (byte >= 0x20) {
byte = encoded[index].code - 63
result = result or ((byte and 0x1f) shl shift)
shift += 5
index++
}
lat += if ((result and 1) > 0) (result shr 1).inv() else (result shr 1)
byte = 0x20
shift = 0
result = 0
while (byte >= 0x20) {
byte = encoded[index].code - 63
result = result or ((byte and 0x1f) shl shift)
shift += 5
index++
}
lng += if ((result and 1) > 0) (result shr 1).inv() else (result shr 1)
coordinates.add(listOf(lng.toDouble() / factor, lat.toDouble() / factor))
}
return coordinates
}
/**
* Calculates the geographic center of the route's GeoJSON data.
*
* @return A [Location] object representing the center point.
* @throws IllegalStateException if the calculated center does not have valid Point geometry.
*/
fun createCenterLocation(routeGeoJson: String): Location {
// 1. Create a FeatureCollection from the raw GeoJSON string.
val featureCollection = FeatureCollection.fromJson(routeGeoJson)
// 2. Calculate the center feature of the collection.
val centerFeature = TurfMeasurement.center(featureCollection)
// 3. Safely access and cast the geometry, throwing an informative error if it fails.
val centerPoint = centerFeature.geometry() as? Point
?: throw IllegalStateException("Center of GeoJSON is not a valid Point.")
// 4. Create and return the Location object.
return location(centerPoint.longitude(), centerPoint.latitude())
}
fun createGeoJson(type : String, lineCoordinates: List<List<Double>>): String {
val lineString = GeoJsonType(type = type, coordinates = lineCoordinates)
val feature = GeoJsonFeature(type = "Feature", geometry = lineString)
val featureCollection =
GeoJsonFeatureCollection(type = "FeatureCollection", features = listOf(feature))
val jsonString = Json.encodeToString(featureCollection)
return jsonString
}
fun getOverpassBbox(location: Location, radius: Double): String {
val bbox = getBoundingBox(location.longitude, location.latitude, radius)
val neLon = bbox["ne"]?.get("lon")
val neLat = bbox["ne"]?.get("lat")
val swLon = bbox["sw"]?.get("lon")
val swLat = bbox["sw"]?.get("lat")
return "$swLon,$swLat,$neLon,$neLat"
}
fun getBoundingBox2(location: Location, radius: Double): BoundingBox {
val bbox = getBoundingBox(location.longitude, location.latitude, radius)
val neLon = bbox["ne"]?.get("lon")
val neLat = bbox["ne"]?.get("lat")
val swLon = bbox["sw"]?.get("lon")
val swLat = bbox["sw"]?.get("lat")
return BoundingBox(swLat ?: 0.0 , swLon ?: 0.0, neLat ?: 0.0, neLon ?: 0.0)
}
fun getBoundingBox(
lat: Double,
lon: Double,
radius: Double
): Map<String, Map<String, Double>> {
val earthRadius = 6371.0
val maxLat = lat + toDegrees(radius / earthRadius)
val minLat = lat - toDegrees(radius / earthRadius)
val maxLon = lon + toDegrees(radius / earthRadius / cos(toRadians(lat)))
val minLon = lon - toDegrees(radius / earthRadius / cos(toRadians(lat)))
return mapOf(
"nw" to mapOf("lat" to maxLat, "lon" to minLon),
"ne" to mapOf("lat" to maxLat, "lon" to maxLon),
"sw" to mapOf("lat" to minLat, "lon" to minLon),
"se" to mapOf("lat" to minLat, "lon" to maxLon)
)
}
}
fun calculateZoom(speed: Double?): Double {
@@ -224,6 +110,17 @@ fun previewZoom(previewDistance: Double): Double {
}
return 9.0
}
fun calcTilt(newZoom: Double, tilt: Double): Double = if (newZoom < 13) {
0.0
} else {
if (tilt == 0.0) {
55.0
} else {
tilt
}
}
fun bearing(fromLocation: Location, toLocation: Location, oldBearing: Double) : Double {
val distance = fromLocation.distanceTo(toLocation)
if (distance < 1.0) {