Amenities GeoUtils
This commit is contained in:
@@ -26,7 +26,7 @@ import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
|
|||||||
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
|
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
|
||||||
import com.kouros.navigation.data.Constants.TAG
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.data.ObjectBox
|
import com.kouros.navigation.data.ObjectBox
|
||||||
import com.kouros.navigation.utils.NavigationUtils.snapLocation
|
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||||
|
|
||||||
class NavigationSession : Session(), NavigationScreen.Listener {
|
class NavigationSession : Session(), NavigationScreen.Listener {
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import com.kouros.navigation.data.Constants
|
|||||||
import com.kouros.navigation.data.ObjectBox
|
import com.kouros.navigation.data.ObjectBox
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.utils.bearing
|
import com.kouros.navigation.utils.bearing
|
||||||
|
import com.kouros.navigation.utils.calcTilt
|
||||||
import com.kouros.navigation.utils.calculateZoom
|
import com.kouros.navigation.utils.calculateZoom
|
||||||
import com.kouros.navigation.utils.duration
|
import com.kouros.navigation.utils.duration
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
@@ -188,36 +189,17 @@ class SurfaceRenderer(
|
|||||||
) {
|
) {
|
||||||
val cameraDuration =
|
val cameraDuration =
|
||||||
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
||||||
var bearing = position.bearing
|
|
||||||
var zoom = position.zoom
|
|
||||||
var target = position.target
|
|
||||||
var localTilt = tilt
|
|
||||||
val currentSpeed: Float? by speed.observeAsState()
|
val currentSpeed: Float? by speed.observeAsState()
|
||||||
when (viewStyle) {
|
if (viewStyle == ViewStyle.VIEW) {
|
||||||
ViewStyle.VIEW -> {
|
|
||||||
DrawImage(paddingValues, currentSpeed, width, height)
|
DrawImage(paddingValues, currentSpeed, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewStyle.PREVIEW -> {
|
|
||||||
bearing = 0.0
|
|
||||||
zoom = previewZoom(previewDistance)
|
|
||||||
target = Position(centerLocation.longitude, centerLocation.latitude)
|
|
||||||
localTilt = 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
bearing = 0.0
|
|
||||||
localTilt = 0.0
|
|
||||||
zoom = 12.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(position, viewStyle) {
|
LaunchedEffect(position, viewStyle) {
|
||||||
cameraState.animateTo(
|
cameraState.animateTo(
|
||||||
finalPosition = CameraPosition(
|
finalPosition = CameraPosition(
|
||||||
bearing = bearing,
|
bearing = position.bearing,
|
||||||
zoom = zoom,
|
zoom = position.zoom,
|
||||||
target = target,
|
target = position.target,
|
||||||
tilt = localTilt,
|
tilt = tilt,
|
||||||
padding = paddingValues
|
padding = paddingValues
|
||||||
),
|
),
|
||||||
duration = cameraDuration
|
duration = cameraDuration
|
||||||
@@ -235,21 +217,15 @@ class SurfaceRenderer(
|
|||||||
/** Handles the map zoom-in and zoom-out events. */
|
/** Handles the map zoom-in and zoom-out events. */
|
||||||
fun handleScale(zoomSign: Int) {
|
fun handleScale(zoomSign: Int) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
|
if (viewStyle == ViewStyle.VIEW) {
|
||||||
viewStyle = ViewStyle.PAN_VIEW
|
viewStyle = ViewStyle.PAN_VIEW
|
||||||
|
}
|
||||||
val newZoom = if (zoomSign < 0) {
|
val newZoom = if (zoomSign < 0) {
|
||||||
cameraPosition.value!!.zoom - 1.0
|
cameraPosition.value!!.zoom - 1.0
|
||||||
} else {
|
} else {
|
||||||
cameraPosition.value!!.zoom + 1.0
|
cameraPosition.value!!.zoom + 1.0
|
||||||
}
|
}
|
||||||
tilt = if (newZoom < 13) {
|
tilt = calcTilt(newZoom, tilt)
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
if (tilt == 0.0) {
|
|
||||||
55.0
|
|
||||||
} else {
|
|
||||||
tilt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateCameraPosition(
|
updateCameraPosition(
|
||||||
cameraPosition.value!!.bearing,
|
cameraPosition.value!!.bearing,
|
||||||
newZoom,
|
newZoom,
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ fun MapLibre(
|
|||||||
@Composable
|
@Composable
|
||||||
fun RouteLayer(routeData: String?, viewStyle: ViewStyle) {
|
fun RouteLayer(routeData: String?, viewStyle: ViewStyle) {
|
||||||
if (routeData != null && routeData.isNotEmpty()) {
|
if (routeData != null && routeData.isNotEmpty()) {
|
||||||
println(routeData)
|
|
||||||
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
|
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
|
||||||
if (viewStyle == ViewStyle.VIEW) {
|
if (viewStyle == ViewStyle.VIEW) {
|
||||||
LineLayer(
|
LineLayer(
|
||||||
@@ -149,10 +148,10 @@ fun RouteLayer(routeData: String?, viewStyle: ViewStyle) {
|
|||||||
source = routes,
|
source = routes,
|
||||||
// Convert a drawable resource to a MapLibre image
|
// Convert a drawable resource to a MapLibre image
|
||||||
// drawAsSdf = true allows us to tint the image programmatically
|
// drawAsSdf = true allows us to tint the image programmatically
|
||||||
iconImage = image(painterResource(R.drawable.ic_place_white_24dp), drawAsSdf = true),
|
iconImage = image(painterResource(com.kouros.android.cars.carappservice.R.drawable.ev_station_24px), drawAsSdf = true),
|
||||||
// Now we can apply any color we want!
|
// Now we can apply any color we want!
|
||||||
iconColor = const(Color.Blue),
|
iconColor = const(Color.Red),
|
||||||
iconSize = const(1.5f)
|
iconSize = const(5.0f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +282,7 @@ fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
|
|||||||
return when (viewStyle) {
|
return when (viewStyle) {
|
||||||
ViewStyle.VIEW -> PaddingValues(start = 50.dp, top = distanceFromTop(height).dp)
|
ViewStyle.VIEW -> PaddingValues(start = 50.dp, top = distanceFromTop(height).dp)
|
||||||
ViewStyle.PREVIEW -> PaddingValues(start = 150.dp, bottom = 0.dp)
|
ViewStyle.PREVIEW -> PaddingValues(start = 150.dp, bottom = 0.dp)
|
||||||
else -> PaddingValues(start = 450.dp, bottom = 0.dp)
|
else -> PaddingValues(start = 250.dp, bottom = 0.dp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class CategoriesScreen(
|
|||||||
|
|
||||||
val header = Header.Builder()
|
val header = Header.Builder()
|
||||||
.setStartHeaderAction(Action.BACK)
|
.setStartHeaderAction(Action.BACK)
|
||||||
.setTitle("title")
|
.setTitle(carContext.getString(R.string.category_title))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return ListTemplate.Builder()
|
return ListTemplate.Builder()
|
||||||
|
|||||||
@@ -23,11 +23,10 @@ import androidx.lifecycle.Observer
|
|||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.navigation.NavigationMessage
|
import com.kouros.navigation.car.navigation.NavigationMessage
|
||||||
import com.kouros.navigation.data.Constants.homeLocation
|
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.overpass.Elements
|
import com.kouros.navigation.data.overpass.Elements
|
||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
import com.kouros.navigation.utils.NavigationUtils.createGeoJson
|
import com.kouros.navigation.utils.GeoUtils.createPointCollection
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@@ -50,11 +49,10 @@ class CategoryScreen(
|
|||||||
loc.longitude = it.lon!!
|
loc.longitude = it.lon!!
|
||||||
loc.latitude = it.lat!!
|
loc.latitude = it.lat!!
|
||||||
}
|
}
|
||||||
if (coordinates.isEmpty())
|
coordinates.add(listOf(it.lon!!, it.lat!!))
|
||||||
coordinates.add(listOf(location.longitude, location.latitude))
|
|
||||||
}
|
}
|
||||||
if (elements.isNotEmpty()) {
|
if (elements.isNotEmpty()) {
|
||||||
val route = createGeoJson("Point", coordinates)
|
val route = createPointCollection(coordinates)
|
||||||
surfaceRenderer.setCategories(loc, route)
|
surfaceRenderer.setCategories(loc, route)
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
@@ -106,7 +104,6 @@ class CategoryScreen(
|
|||||||
.setStartHeaderAction(Action.BACK)
|
.setStartHeaderAction(Action.BACK)
|
||||||
.setTitle(carContext.getString(R.string.charging_station))
|
.setTitle(carContext.getString(R.string.charging_station))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val builder = MapWithContentTemplate.Builder()
|
val builder = MapWithContentTemplate.Builder()
|
||||||
.setContentTemplate(
|
.setContentTemplate(
|
||||||
ListTemplate.Builder()
|
ListTemplate.Builder()
|
||||||
|
|||||||
@@ -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
|
@Serializable
|
||||||
data class Locations (
|
data class Locations (
|
||||||
var lat : Double,
|
var lat : Double,
|
||||||
|
|||||||
@@ -6,14 +6,11 @@ import com.kouros.navigation.data.valhalla.Maneuvers
|
|||||||
import com.kouros.navigation.data.valhalla.Summary
|
import com.kouros.navigation.data.valhalla.Summary
|
||||||
import com.kouros.navigation.data.valhalla.Trip
|
import com.kouros.navigation.data.valhalla.Trip
|
||||||
import com.kouros.navigation.data.valhalla.ValhallaJson
|
import com.kouros.navigation.data.valhalla.ValhallaJson
|
||||||
import com.kouros.navigation.utils.NavigationUtils.createCenterLocation
|
import com.kouros.navigation.utils.GeoUtils.createCenterLocation
|
||||||
import com.kouros.navigation.utils.NavigationUtils.createGeoJson
|
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
|
||||||
import com.kouros.navigation.utils.NavigationUtils.decodePolyline
|
import com.kouros.navigation.utils.GeoUtils.decodePolyline
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import org.maplibre.geojson.FeatureCollection
|
|
||||||
import org.maplibre.geojson.Point
|
import org.maplibre.geojson.Point
|
||||||
import org.maplibre.turf.TurfMeasurement
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
|
|
||||||
data class Route(
|
data class Route(
|
||||||
@@ -95,7 +92,7 @@ data class Route(
|
|||||||
points.add(point)
|
points.add(point)
|
||||||
}
|
}
|
||||||
pointLocations = points
|
pointLocations = points
|
||||||
routeGeoJson = createGeoJson("LineString", waypoints)
|
routeGeoJson = createLineStringCollection( waypoints)
|
||||||
centerLocation = createCenterLocation(routeGeoJson)
|
centerLocation = createCenterLocation(routeGeoJson)
|
||||||
return Route(
|
return Route(
|
||||||
maneuvers,
|
maneuvers,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.kouros.navigation.data.overpass
|
|||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import com.google.gson.GsonBuilder
|
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 com.kouros.navigation.utils.NavigationUtils
|
||||||
import java.io.OutputStreamWriter
|
import java.io.OutputStreamWriter
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
@@ -11,8 +13,8 @@ class Overpass {
|
|||||||
|
|
||||||
val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
|
val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
|
||||||
fun getAmenities(category: String, location: Location) : List<Elements> {
|
fun getAmenities(category: String, location: Location) : List<Elements> {
|
||||||
val boundingBox = NavigationUtils.getOverpassBbox(location, 2.0)
|
val boundingBox = getOverpassBbox(location, 2.0)
|
||||||
val bb = NavigationUtils.getBoundingBox2(location, 2.0)
|
val bb = getBoundingBox2(location, 2.0)
|
||||||
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
|
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
|
||||||
httpURLConnection.requestMethod = "POST"
|
httpURLConnection.requestMethod = "POST"
|
||||||
httpURLConnection.setRequestProperty(
|
httpURLConnection.setRequestProperty(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,12 +6,13 @@ import android.location.LocationManager
|
|||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.kouros.navigation.data.BoundingBox
|
import com.kouros.navigation.data.BoundingBox
|
||||||
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
|
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.FeatureCollection
|
||||||
import org.maplibre.geojson.Point
|
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.TurfMeasurement
|
||||||
import org.maplibre.turf.TurfMisc
|
import org.maplibre.turf.TurfMisc
|
||||||
import java.lang.Math.toDegrees
|
import java.lang.Math.toDegrees
|
||||||
@@ -77,121 +78,6 @@ object NavigationUtils {
|
|||||||
apply()
|
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 {
|
fun calculateZoom(speed: Double?): Double {
|
||||||
@@ -224,6 +110,17 @@ fun previewZoom(previewDistance: Double): Double {
|
|||||||
}
|
}
|
||||||
return 9.0
|
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 {
|
fun bearing(fromLocation: Location, toLocation: Location, oldBearing: Double) : Double {
|
||||||
val distance = fromLocation.distanceTo(toLocation)
|
val distance = fromLocation.distanceTo(toLocation)
|
||||||
if (distance < 1.0) {
|
if (distance < 1.0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user