I'm using Google Places api in my app which calculate the distance between 2 address,
the problem is that I get less distance than in google maps.
I cannot get the same accuracy as Google maps,
Most of the time distance is shorter than the result from Google Maps
private fun SetupPlacesAutocompleteFun() {
val _autocompletFragment1 = supportFragmentManager
.findFragmentById(R.id.fragmentPlaces1) as AutocompleteSupportFragment
_autocompletFragment1.setPlaceFields(_placesFields)
_autocompletFragment1.setOnPlaceSelectedListener(object:PlaceSelectionListener{
override fun onPlaceSelected(p1: Place) {
_adrees1 = p1.latLng!!
_adressString1 = p1.address!!
}
override fun onError(p1: Status) {
Toast.makeText(this#GooglePlaces_Activity,"status "+p1.statusMessage,Toast.LENGTH_LONG).show()
}
})
val _autocompletFragment2 = supportFragmentManager
.findFragmentById(R.id.fragmentPlaces2) as AutocompleteSupportFragment
_autocompletFragment2.setPlaceFields(_placesFields)
_autocompletFragment2.setOnPlaceSelectedListener(object :PlaceSelectionListener{
override fun onPlaceSelected(p2: Place) {
_adress2 =p2.latLng!!
_adressString2 = p2.address!!
}
override fun onError(p2: Status) {
Toast.makeText(this#GooglePlaces_Activity,"status "+p2.statusMessage,Toast.LENGTH_LONG).show()
}
})
}
private fun InitPlacesFun() {
Places.initialize(this,getString(R.string.Places_api))
_placcesClint = Places.createClient(this)
}
fun TestButtonFun () {
button.setOnClickListener() {
var _loc:Location= Location("start")
_loc.latitude = _adrees1.latitude
_loc.longitude = _adrees1.longitude
var _loc2:Location= Location("start")
_loc2.latitude = _adress2.latitude
_loc2.longitude = _adress2.longitude
dist = (round(((_loc.distanceTo(_loc2).toDouble()/1000))*100) /100).toDouble()
}
I have also tried this:
var _Lat1 = _adrees1.latitude
var _Lat2 = _adress2.latitude
var _Long1 = _adrees1.longitude
var _Long2 = _adress2.longitude
var _LatRes = _Lat1 - _Lat2
var _longRes = _Long1 - _Long2
var R = 6371000f; // Radius of the earth in m
var dLat = _LatRes * Math.PI / 180f;
var dLon = _longRes * Math.PI / 180f;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(_Lat1 * Math.PI / 180f) * Math.cos(_Lat2 * Math.PI / 180f) *
Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2f * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
dist = R* c
You get less distance because you're calculating the radial distance and not the actual route distance.
Try calculating the distance using google directions API.
You can see the difference between radial distance (orange) and actual distance (blue) in the image
Related
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)
}
`
I'm trying to draw the label lines as in picture using MPAndroidChart with a pie chart. I can't figure out how to
decouple the lines from the chart
draw that little circle at the beginning of the line.
Thank you.
This is by no means easy to achieve. To decouple the lines from the chart, you can use valueLinePart1OffsetPercentage and play with line part lengths. But to get the chart to draw dots at the end of lines, you need a custom renderer. Here's one:
class CustomPieChartRenderer(pieChart: PieChart, val circleRadius: Float)
: PieChartRenderer(pieChart, pieChart.animator, pieChart.viewPortHandler) {
override fun drawValues(c: Canvas) {
super.drawValues(c)
val center = mChart.centerCircleBox
val radius = mChart.radius
var rotationAngle = mChart.rotationAngle
val drawAngles = mChart.drawAngles
val absoluteAngles = mChart.absoluteAngles
val phaseX = mAnimator.phaseX
val phaseY = mAnimator.phaseY
val roundedRadius = (radius - radius * mChart.holeRadius / 100f) / 2f
val holeRadiusPercent = mChart.holeRadius / 100f
var labelRadiusOffset = radius / 10f * 3.6f
if (mChart.isDrawHoleEnabled) {
labelRadiusOffset = (radius - radius * holeRadiusPercent) / 2f
if (!mChart.isDrawSlicesUnderHoleEnabled && mChart.isDrawRoundedSlicesEnabled) {
rotationAngle += roundedRadius * 360 / (Math.PI * 2 * radius).toFloat()
}
}
val labelRadius = radius - labelRadiusOffset
val dataSets = mChart.data.dataSets
var angle: Float
var xIndex = 0
c.save()
for (i in dataSets.indices) {
val dataSet = dataSets[i]
val sliceSpace = getSliceSpace(dataSet)
for (j in 0 until dataSet.entryCount) {
angle = if (xIndex == 0) 0f else absoluteAngles[xIndex - 1] * phaseX
val sliceAngle = drawAngles[xIndex]
val sliceSpaceMiddleAngle = sliceSpace / (Utils.FDEG2RAD * labelRadius)
angle += (sliceAngle - sliceSpaceMiddleAngle / 2f) / 2f
if (dataSet.valueLineColor != ColorTemplate.COLOR_NONE) {
val transformedAngle = rotationAngle + angle * phaseY
val sliceXBase = cos(transformedAngle * Utils.FDEG2RAD.toDouble()).toFloat()
val sliceYBase = sin(transformedAngle * Utils.FDEG2RAD.toDouble()).toFloat()
val valueLinePart1OffsetPercentage = dataSet.valueLinePart1OffsetPercentage / 100f
val line1Radius = if (mChart.isDrawHoleEnabled) {
(radius - radius * holeRadiusPercent) * valueLinePart1OffsetPercentage + radius * holeRadiusPercent
} else {
radius * valueLinePart1OffsetPercentage
}
val px = line1Radius * sliceXBase + center.x
val py = line1Radius * sliceYBase + center.y
if (dataSet.isUsingSliceColorAsValueLineColor) {
mRenderPaint.color = dataSet.getColor(j)
}
c.drawCircle(px, py, circleRadius, mRenderPaint)
}
xIndex++
}
}
MPPointF.recycleInstance(center)
c.restore()
}
}
This custom renderer extends the default pie chart renderer. I basically just copied the code from PieChartRenderer.drawValues method, converted it to Kotlin, and removed everything that wasn't needed. I only kept the logic needed to determine the position of the points at the end of lines.
I tried to reproduce the image you showed:
val chart: PieChart = view.findViewById(R.id.pie_chart)
chart.setExtraOffsets(40f, 0f, 40f, 0f)
// Custom renderer used to add dots at the end of value lines.
chart.renderer = CustomPieChartRenderer(chart, 10f)
val dataSet = PieDataSet(listOf(
PieEntry(40f),
PieEntry(10f),
PieEntry(10f),
PieEntry(15f),
PieEntry(10f),
PieEntry(5f),
PieEntry(5f),
PieEntry(5f)
), "Pie chart")
// Chart colors
val colors = listOf(
Color.parseColor("#4777c0"),
Color.parseColor("#a374c6"),
Color.parseColor("#4fb3e8"),
Color.parseColor("#99cf43"),
Color.parseColor("#fdc135"),
Color.parseColor("#fd9a47"),
Color.parseColor("#eb6e7a"),
Color.parseColor("#6785c2"))
dataSet.colors = colors
dataSet.setValueTextColors(colors)
// Value lines
dataSet.valueLinePart1Length = 0.6f
dataSet.valueLinePart2Length = 0.3f
dataSet.valueLineWidth = 2f
dataSet.valueLinePart1OffsetPercentage = 115f // Line starts outside of chart
dataSet.isUsingSliceColorAsValueLineColor = true
// Value text appearance
dataSet.yValuePosition = PieDataSet.ValuePosition.OUTSIDE_SLICE
dataSet.valueTextSize = 16f
dataSet.valueTypeface = Typeface.DEFAULT_BOLD
// Value formatting
dataSet.valueFormatter = object : ValueFormatter() {
private val formatter = NumberFormat.getPercentInstance()
override fun getFormattedValue(value: Float) =
formatter.format(value / 100f)
}
chart.setUsePercentValues(true)
dataSet.selectionShift = 3f
// Hole
chart.isDrawHoleEnabled = true
chart.holeRadius = 50f
// Center text
chart.setDrawCenterText(true)
chart.setCenterTextSize(20f)
chart.setCenterTextTypeface(Typeface.DEFAULT_BOLD)
chart.setCenterTextColor(Color.parseColor("#222222"))
chart.centerText = "Center\ntext"
// Disable legend & description
chart.legend.isEnabled = false
chart.description = null
chart.data = PieData(dataSet)
Again, not very straightforward. I hope you like Kotlin! You can move most of that configuration code to a subclass if you need it often. Here's the result:
I'm not a MPAndroidChart expert. In fact, I've used it only once, and that was 2 years ago. But if you do your research, you can find a solution most of the time. Luckily, MPAndroidChart is a very customizable.
I am building an Android application to measure 1km from a start position while driving.
i tried this : https://github.com/quentin7b/android-location-tracker/blob/master/README.md
val settings = TrackerSettings()
.setUseNetwork(true)
.setUseGPS(true)
.setUsePassive(false)
.setTimeBetweenUpdates(1000)
.setMetersBetweenUpdates(10F)
And i change postions to get distance between to position using:
val distance = currentPosition!!.distanceTo(location)
totalDistance += distance
But the result is not precise, anyone how can give a better solution
private fun distance(
startLatitude: Double,
startLongitude: Double,
endLatitude: Double,
endLongitude: Double,
unit: String
): String {
val theta = startLongitude - endLongitude
var dist: Double = sin(deg2rad(startLatitude)) * sin(deg2rad(endLatitude)) +
cos(deg2rad(startLatitude)) * cos(deg2rad(endLatitude)) * cos(deg2rad(theta))
dist = acos(dist)
dist = rad2deg(dist)
dist *= 60 * 1.1515 //miles
if (unit == "k") {
dist *= 1.609344 // km
}
return dist.toString()
}
private fun deg2rad(deg: Double): Double {
return deg * 3.1415926535897932 / 180.0
}
private fun rad2deg(rad: Double): Double {
return rad * 180.0 / 3.1415926535897932
}
You can use android.location.Location.distanceBetween() method which does this quite well. For more info
I'm trying to implement a pie chart as shown in pictures below, where corners of the arc should be rounded.
I've tried to use CornerPathEffect(), but it seems to work only on the intersection of two lines (path.lineTo()). Nothing changes if I use this method for arc (path.arcTo()).
Try to set Stroke Cap of paint.
mPaint.setStrokeCap(Paint.Cap.ROUND);
I know it's too late for this answer but here is my solution.
PieSlice.kt
data class PieSlice(
val name: String,
var value: Double,
var startAngle: Float,
var sweepAngle: Float,
var indicatorCircleLocation: PointF,
val paint: Paint
)
Function to make round corner arc
private fun drawCurvedArc(canvas: Canvas?, pieItem: PieSlice) {
val path = Path()
path.moveTo(originX, originY)
val angleStart = pieItem.startAngle
val angleEnd = (pieItem.startAngle - pieItem.sweepAngle)
val arcOffset = pieItem.sweepAngle.coerceAtMost(7f)
val lineOffset = if (pieItem.sweepAngle < 7f) 0f else 25f
// line from origin to top
val line1x = getPointX(angleStart, lineOffset)
val line1y = getPointY(angleStart, lineOffset)
path.lineTo(line1x, line1y)
//Curve corner from line top to arc start
val arcStartx = getPointX(angleStart - arcOffset)
val arcStarty = getPointY(angleStart - arcOffset)
joinLineAndArc(path, line1x, line1y, arcStartx, arcStarty)
//Arc
path.arcTo(
outerRect, (pieItem.startAngle - arcOffset),
(-pieItem.sweepAngle + 2 * arcOffset), true
)
val line2x = getPointX(angleEnd, lineOffset)
val line2y = getPointY(angleEnd, lineOffset)
val arcEndx = getPointX(angleEnd + arcOffset)
val arcEndy = getPointY(angleEnd + arcOffset)
//Curve corner from arc end to bottom line
joinLineAndArc(path, arcEndx, arcEndy, line2x, line2y)
// Bottom line
path.lineTo(originX, originY)
val borderPaint = Paint()
borderPaint.strokeJoin = Paint.Join.ROUND
borderPaint.strokeCap = Paint.Cap.ROUND
borderPaint.color = pieItem.paint.color
borderPaint.style = Paint.Style.FILL
borderPaint.strokeWidth = 0f
canvas?.drawPath(path, borderPaint)
}
/**
* Join line and arc with a curve
*
* vector = (x1-x2,y1-y2)
*
* pVector perpendicular to vector
* pVector = (y1-y2,x2-x1)
*
* midX = (x1+x2)/2
* midY = (y1+y2)/2
*
* (px,py) = (midX,midY) ± (D/√((y1−y2)^2,(x2−x1)^2))*(y1-y2,x2-x1)
*/
private fun joinLineAndArc(
path: Path,
x1: Float,
y1: Float,
x2: Float,
y2: Float
) {
val midX: Float = (x2 + x1) / 2f
val midY: Float = (y2 + y1) / 2f
val x2_x1 = (x2 - x1).toDouble()
val y1_y2 = (y1 - y2).toDouble()
val powY = y1_y2.pow(2.0)
val powX = x2_x1.pow(2.0)
val den = sqrt(powY + powX)
val len = 20.0
// perpendicular1
val p1x = (midX + ((len * y1_y2) / den)).toFloat()
val p1y = (midY + ((len * x2_x1) / den)).toFloat()
// perpendicular2
val p2x = (midX - ((len * y1_y2) / den)).toFloat()
val p2y = (midY - ((len * x2_x1) / den)).toFloat()
val len1 = Math.sqrt(
Math.pow((originX - p1x).toDouble(), 2.0)
+ Math.pow((originY - p1y).toDouble(), 2.0)
)
val len2 = Math.sqrt(
Math.pow((originX - p2x).toDouble(), 2.0)
+ Math.pow((originY - p2y).toDouble(), 2.0)
)
//Make a curve to the point which is far from origin
if (len1 > len2) {
path.cubicTo(x1, y1, p1x, p1y, x2, y2)
} else {
path.cubicTo(x1, y1, p2x, p2y, x2, y2)
}
}
/**
* Get the x coordinate on a circle
* formula for x pos: (radius) * cos(angle) + (distance from left edge of screen)
* #param angle angle of point from origin
* #param offset to make shorter line than actual radius
*/
private fun getPointX(angle: Float, offset: Float = 0f): Float {
return ((radius - offset)
* cos(Math.toRadians((angle.toDouble())))
+ originX).toFloat()
}
/**
* Get the y coordinate on a circle
* formula for y pos: (radius) * sin(angle) + (distance from top edge of screen)
*
* #param angle angle of point from origin
* #param offset to make shorter line than actual radius
*/
private fun getPointY(angle: Float, offset: Float = 0f): Float {
return ((radius - offset)
* sin(Math.toRadians((angle.toDouble())))
+ originY).toFloat()
}
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.