I have created an application that, given two input points, calls a function that draws a path, on a map, between the two points. I would now like to be able to implement the ability to click on the path and save it locally, in a list, so that I can retrieve it when necessary.
I'm a beginner with programming in kotlin and android, could you advise me how to do it?
I share below the function that I use to create the path in case it is useful.
fun routePath(p1Latit: Double, p1Laong: Double, p2Latit: Double, p2Laong: Double){
val roadManager: RoadManager = OSRMRoadManager(context, "lolloMaps")
OSRMRoadManager.MEAN_BY_FOOT
val waypoints = arrayListOf<GeoPoint>()
val startPoint: GeoPoint = GeoPoint(p1Latit, p1Laong)
waypoints.add(startPoint)
val endPoint: GeoPoint = GeoPoint(p2Latit, p2Laong)
waypoints.add(endPoint)
mapView.overlays.forEach{
if (it is Polyline && it.id == "Path"){
mapView.overlays.remove(it)
}
}
road = roadManager.getRoad(waypoints)
if (road.mStatus != Road.STATUS_OK){
Toast.makeText(context, "Error - status = " + road.mStatus, Toast.LENGTH_SHORT).show()
}
val roadOverlay: Polyline = RoadManager.buildRoadOverlay(road)
roadOverlay.id = "Path"
mapView.overlays.add(roadOverlay)
mapView.invalidate()
val nodeIcon: Drawable? = ResourcesCompat.getDrawable(mapView.resources, R.drawable.marker_node, null)
mapView.overlays.forEach{
if (it is Marker && it.id == "Node"){
mapView.overlays.remove(it)
}
}
for (i: Int in road.mNodes.indices){
val node: RoadNode = road.mNodes[i]
val nodeMarker: Marker = Marker(mapView)
nodeMarker.position = node.mLocation
nodeMarker.icon = nodeIcon
nodeMarker.title = "Passo $i"
nodeMarker.id = "Node"
mapView.overlays.add(nodeMarker)
nodeMarker.snippet = node.mInstructions
nodeMarker.subDescription = Road.getLengthDurationText(context,node.mLength, node.mDuration)
// var icon: Drawable = resources.getDrawable(R.drawable.ic_continue)
// nodeMarker.image = icon
}
}
Related
I have the following problem.
I receive in my LocationChangeListeningCallback latitude and longitude but getting the speed is not possible. It is always 0.
Here is my code (I hope I have not omitted anything).
AndroidManidest.xml
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
build.gradle
//MapBox
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:8.6.2'
implementation('com.google.android.gms:play-services-location:18.0.0')
MainActivity.kt
override fun onMapReady(mapboxMap: MapboxMap) {
map = mapboxMap
callback = LocationChangeListeningCallback()
mapboxMap.setStyle(Style.MAPBOX_STREETS) {
enableLocationComponent(it)
}
}
private inner class LocationChangeListeningCallback : LocationEngineCallback {
override fun onSuccess(result: LocationEngineResult?) {
result?.lastLocation ?: return
if (result.lastLocation != null){
val lat = result.lastLocation?.latitude!!
val lng = result.lastLocation?.longitude!!
val latLng = LatLng(lat, lng)
if (result.lastLocation != null) {
map.locationComponent.forceLocationUpdate(result.lastLocation)
val position = CameraPosition.Builder()
.target(latLng)
.zoom(13.0)
.tilt(10.0)
.build()
map.animateCamera(CameraUpdateFactory.newCameraPosition(position))
point = Point.fromLngLat(lng, lat)
pointList.add(point)
if(pointList.size>1) {
var location = Location(LocationManager.GPS_PROVIDER)
val results = FloatArray(5)
val lastlat = pointList[pointList.lastIndex - 1]
var xLng = lastlat.coordinates()[0]
var yLat = lastlat.coordinates()[1]
Location.distanceBetween(yLat, xLng, lat, lng, results)
//--> Here speed is always 0.0
if(location.hasSpeed())
speed = location.speed/18*5
else
speed = 0F
accDistance += results[0]
textViewDistance.text = "Distance in meters covered: " + accDistance + "\n" + "Current Velocity: " + speed
} else {
textViewDistance.text = "Distance in meters covered: 0.0" + "\n" + "Current Velocity: 0"
}
map_view.getMapAsync(OnMapReadyCallback { mapboxMap ->
mapboxMap.setStyle(Style.MAPBOX_STREETS) { style ->
// Create the LineString from the list of coordinates and then make a GeoJSON
// FeatureCollection so we can add the line to our map as a layer
style.addSource(
GeoJsonSource(
"line-source",
FeatureCollection.fromFeatures(
arrayOf(
Feature.fromGeometry(
LineString.fromLngLats(pointList)
)
)
)
)
)
// The layer properties for our line
style.addLayer(
LineLayer("linelayer", "line-source").withProperties(
PropertyFactory.lineWidth(3f),
PropertyFactory.lineColor(Color.parseColor("#FF0000"))
)
)
}
})
}
}
}
override fun onFailure(exception: Exception) {}
}
I hope somebody can help me.
I have not found anything on the Internet to solve my problem.
Sup, guys! I have a task to calculate coordinates of continuation line between points A & B. I have a code each calculate distance between these points, and I have tangent of angle for calculating next points D and F. Thanks for any help
`val pointA = LatLng(a.latitude, a.longitude)
val pointB = LatLng(b.latitude, b.longitude)
val pointC = LatLng(a.latitude, b.longitude)//for calculating
val polylineOptions = PolylineOptions()
.add(pointA)
.add(pointB)
.add(pointC)
.add(pointA)
val distanceAB = getKmFromLatLong(pointA.latitude, pointA.longitude, pointB.latitude, pointB.longitude)
val distanceBC = getKmFromLatLong(pointB.latitude, pointB.longitude, pointC.latitude, pointC.longitude)
val distanceCA = getKmFromLatLong(pointC.latitude, pointC.longitude, pointA.latitude, pointA.longitude)
val tang = distanceBC/distanceCA
println("__ distanceAB $distanceAB")
println("__ distanceBC $distanceBC")
println("__ distanceCA $distanceCA")
println("__ tang $tang")`
Thanks, guys! Point-slope formula - rules
private fun generateNeedingPoints(a: LatLng, b: LatLng): List<LatLng> {
setScreenBounds()
val c: LatLng
val d: LatLng
val longitudeC = if (a.longitude < b.longitude) northEast.longitude else southWest.longitude
val longitudeD = if (a.longitude < b.longitude) southWest.longitude else northEast.longitude
c = calculateEdgeButton(a, b, longitudeC)
d = calculateEdgeButton(b, a, longitudeD)
return mutableListOf(d, a, b, c)
}
`
private fun calculateEdgeButton(a: LatLng, b: LatLng, edgeLng: Double, distortionCoefficient:Double = 1.0): LatLng{
val longChange = b.longitude - a.longitude
val latChange = b.latitude - a.latitude
val slope = latChange / longChange
val longChangePointC = edgeLng - b.longitude
val latChangePointC = longChangePointC * slope
val latitudeC = b.latitude + latChangePointC
return LatLng(latitudeC * distortionCoefficient, edgeLng)
}
`
how can I get the Distance out of Mapbox Navigation Route? in Double Format, I don't want to draw the route I just need to calculate the distance between two Points, I have been able to calculate the distance using Truf
Storelocation = Point.fromLngLat(Stores.latitude, Stores.longitude)
Userlocation = Point.fromLngLat(UserLocation.Latitude, UserLocation.Longitude)
Distance = TurfMeasurement.distance(Storelocation, Userlocation, TurfConstants.UNIT_KILOMETERS)
but the problem is with this method above it doesn't calculate distance within the route, it is just calculate straight line from point to point for example in Google map the distance is 9 Km, but with this method above the distance is 6 Km
private fun getRout(){
NavigationRoute.builder(this)
.accessToken(Mapbox.getAccessToken()!!)
.origin(Userlocation)
.destination(Storelocation).build().getRoute(object :Callback<DirectionsResponse> {
override fun onResponse(call: Call<DirectionsResponse>, response: Response<DirectionsResponse>) {
val rout = response ?: return
val body = rout.body() ?: return
if (body.routes().count() == 0 ){
return
}
navigationMapRoute = NavigationMapRoute(null, mapView, map)
// Get Distance
Distance = ??
}
override fun onFailure(call: Call<DirectionsResponse>, t: Throwable) {
}
})
}
Instead of this:
navigationMapRoute = NavigationMapRoute(null, mapView, map)
// Get Distance
Distance = ??
Do this:
val route = response.body().routes().get(0)
val distance = route.distance()
I used MapGestureListener to compare each time the coords of the clicked area and the coords of the marker and if they're at the same coords then I'm good to go but it just won't work because of the relative altitude change that doesn't assure the accuracy of getting the clicked position.
mpView.addMapGestureListener(object : MapGestureAdapter() {
override fun onMapClicked(e: MotionEvent?, isTwoFingers: Boolean): Boolean {
val clickedArea=mpView.geoCoordinatesFromPoint(Math.round(e!!.getX()), Math.round(e.getY()))
for (marker : MapMarker in markerList )
{
val dist=clickedArea!!.distanceTo(marker.position)
if (dist< 2)
{
val positionMarker = markerList.indexOf(marker)
val positionLastMarker = markerList.indexOf(mSelectedMarker!!)
val markerNumber = positionMarker +1
val lastMarkerNumber = positionLastMarker + 1
travelStep = travelStepList.get(markerNumber -1)
configTeaser(travelStep)
}
}
return false
}
})
I've managed to do it , i just had to call the "requestObjectsAtPoint" inside the MapGesture listener and do some workaround ,here's the code :
mpView.addMapGestureListener(object : MapGestureAdapter() {
override fun onMapClicked(e: MotionEvent?, isTwoFingers: Boolean): Boolean {
mpView.requestObjectsAtPoint(e!!.getX(),e.getY(), RequestObjectCallback { objects, x, y ->
for (marker : ViewObject in objects )
{
if (marker.objectType==1)
{
if ((marker as MapObject).mapObjectType==1)
{
val positionMarker = markerList.indexOf(marker)
val positionLastMarker = markerList.indexOf(mSelectedMarker!!)
val markerNumber = positionMarker +1
val lastMarkerNumber = positionLastMarker + 1
mSelectedMarker = marker as MapMarker
travelStep = travelStepList.get(markerNumber -1)
configTeaser(travelStep)
}
}
}
})
return true
}
})
I am building some app like image below, I want to force markers not to be clickable, but there is no setClickable(false) for Marker or MarkerOptions.
Currently area around marker (see attachment) is not clickable ( click is passed to marker, not map)
You have to use Overlay instead of marker in the Map to get exactly what you desire. You could follow this link, similar is done in JavaScript here.
I found a way to manually handle clicks for markers.
Add a touchable wrapper as described in this stackoverflow answer: https://stackoverflow.com/a/58039285/1499750
Add a gesture detector to your fragment and listen to single taps, then find the closest marker based on lat lng:
private var gestureDetector: GestureDetector? = null
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
gestureDetector = GestureDetector(context, GoogleMapsGestureListener { e -> onMapSingleTap(e) })
//id of touchable wrapper - can use findViewById here instead if not using kotlin synthetics
googleMapsTouchableWrapper?.onTouch = {
gestureDetector?.onTouchEvent(it)
}
}
private fun onMapSingleTap(e: MotionEvent) {
val latLng = map?.projection?.fromScreenLocation(Point(e.x.toInt(), e.y.toInt())) ?: return
//this assumes you are maintaining a set of the latlngs for your markers
val closestNearbyLatLng = markerLatLngs?.findClosestNearbyLatLng(latLng)
//assuming you have a map of latlng to marker you can now find that marker based on latlng and do whatever you need to with it
}
private fun Set<LatLng>.findClosestNearbyLatLng(latLng: LatLng): LatLng? {
val map = map ?: return null
val screenDistance = map.projection.visibleRegion.latLngBounds.northeast.distanceBetweenInKm(map.projection.visibleRegion.latLngBounds.southwest)
val closestLatLng = this.minBy { latLng.distanceBetweenInKm(it) } ?: return null
if (latLng.distanceBetweenInKm(closestLatLng) < screenDistance/40) {
return closestLatLng
}
return null
}
fun LatLong.distanceBetweenInKm(latLng: LatLng): Double {
if (this == latLng) {
return 0.0
}
val earthRadius = 6371.0 //km value;
//converting to radians
val latPoint1Radians = Math.toRadians(latitude)
val lngPoint1Radians = Math.toRadians(longitude)
val latPoint2Radians = Math.toRadians(latLng.latitude)
val lngPoint2Radians = Math.toRadians(latLng.longitude)
var distance = sin((latPoint2Radians - latPoint1Radians) / 2.0).pow(2.0) + (cos(latPoint1Radians) * cos(latPoint2Radians)
* sin((lngPoint2Radians - lngPoint1Radians) / 2.0).pow(2.0))
distance = 2.0 * earthRadius * asin(sqrt(distance))
return abs(distance) //km value
}
class GoogleMapsGestureListener(private val onSingleTap: (MotionEvent) -> Unit) : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
super.onSingleTapConfirmed(e)
e?.let { onSingleTap(it) }
return true
}
}
I recently was able to create a formula to create an area surrounding a certain position on a Google Map, that is also scalable with zoom level.
Here I converted the LatLng coordinates from the marker to actual coordinates on the phone:
//array that holds all locations of every marker
//after a marker is created add the position in here
val positionList = mutableListOf<LatLng>()
//map is variable type GoogleMap
map.setOnMapClickListener {
var inRange = false
for(i in positionList.indices) {
//establish corners of boundaries surrounding markers
val points = positionList.toCoordinates(map)
//check if clicked position falls in one of the positions' bounds
val isInRangeLng = (points[i][2]..points[i][3]).contains(it.longitude)
val isInRangeLat = (points[i][0]..points[i][1]).contains(it.latitude)
//if click lands in of the positions' bounds, stop loop and return inRange
//true
if(isInRangeLat && isInRangeLng) {
inRange = true
break
}
}
if(!inRange) {
//APPLY YOUR LOGIC IF CLICK WAS NOT IN AREA
} else {
//APPLY YOUR LOGIC IF CLICK WAS IN AREA
}
}
//Extension function used to simplify logic
/** Convert LatLng to coordinates on phone **/
fun List<LatLng>.toCoordinates(map: GoogleMap): List<List<Double>> {
val proj: Projection = map.projection
val coordinateList = mutableListOf<List<Double>>()
//create bounds for each position in list
this.forEach {
//get screen coordinates at the current LatLng
val point = proj.toScreenLocation(it)
val left = point.x - 100
val right = point.x + 100
val top = point.y - 100
val bottom = point.y + 100
//convert bounds into two points diagonal of each other
val topRight = Point(right, top)
val bottomLeft = Point(left, bottom)
//convert the two points into LatLng points and get the bounds in north,
//south, west, and east
val northEast = proj.fromScreenLocation(topRight)
val north = northEast.latitude
val east = northEast.longitude
val southWest = proj.fromScreenLocation(bottomLeft)
val south = southWest.latitude
val west = southWest.longitude
//add the bounds to be returned in a list which corresponds to a certain
//position
coordinateList.add(listOf(
south,
north,
west,
east
))
}
return coordinateList
}
This can be used for a lot more than markers too.