1.修复轨迹和宠物有时不在一条线上的bug
2.增加pet页切换刷新运动数据 3.修复绑定设备激活重启,此时点击太快进入wifi 搜索不到bug 4.蓝牙透传通路由Raw TCP改为Https
This commit is contained in:
@@ -28,9 +28,9 @@ android {
|
||||
applicationId "com.abbidot.tracker"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 35
|
||||
versionCode 2216
|
||||
// versionName "2.2.16"
|
||||
versionName "2.2.16-Beta1"
|
||||
versionCode 2300
|
||||
// versionName "2.3.0"
|
||||
versionName "2.3.0-Beta1"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.abbidot.baselibrary.constant.MMKVKey
|
||||
import com.abbidot.baselibrary.util.MMKVUtil
|
||||
import com.abbidot.baselibrary.util.Utils
|
||||
import com.abbidot.tracker.base.BaseDiffBean
|
||||
import com.abbidot.tracker.retrofit2.INetworkService
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
@@ -38,12 +39,13 @@ data class PetBean(
|
||||
var availableOrder: Int,//判断套餐是否可用或过期,1是可用 0不可用
|
||||
var latitude: Double,
|
||||
var longitude: Double,
|
||||
var serverDomain: String,
|
||||
var deviceId: String
|
||||
) : Parcelable, BaseDiffBean() {
|
||||
constructor() : this(
|
||||
"", "", "", "", 0, 1, MMKVUtil.getString(MMKVKey.UserId), "", 0, Utils.formatTime(
|
||||
System.currentTimeMillis(), Utils.DATE_FORMAT_PATTERN_CN
|
||||
), "", 0f, 0f, "", "", "", 1, 1, "", false, false, 1, 0.0, 0.0, ""
|
||||
), "", 0f, 0f, "", "", "", 1, 1, "", false, false, 1, 0.0, 0.0, INetworkService.BASE_URL_BLE, ""
|
||||
)
|
||||
|
||||
override fun isSameObject(other: Any): Boolean {
|
||||
|
||||
@@ -37,6 +37,10 @@ import com.abbidot.tracker.bean.UserInfoBean
|
||||
import com.abbidot.tracker.bean.WXPayOrderBean
|
||||
import com.abbidot.tracker.bean.WiFiZoneListBean
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.GET
|
||||
@@ -44,6 +48,7 @@ import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.Url
|
||||
|
||||
interface INetworkService {
|
||||
|
||||
@@ -57,6 +62,9 @@ interface INetworkService {
|
||||
//亚马逊服务器(国外ip)
|
||||
const val BASE_URL_ABROAD = "https://aws.abbidot.com/abbidot/"
|
||||
|
||||
//设备透传
|
||||
const val BASE_URL_BLE = "https://ab-server.abbidot.cn/"
|
||||
|
||||
//测试服务器
|
||||
const val BASE_URL_TEST = "https://aws.abbidot.com:8443/abbidot/"
|
||||
// const val BASE_URL_TEST = "http://aws.abbidot.com:8089/abbidot/"
|
||||
@@ -80,6 +88,13 @@ interface INetworkService {
|
||||
// const val BASE_URL_CN1 = "$SERVER_IP_CN/abbidotServer/"
|
||||
}
|
||||
|
||||
/**
|
||||
* 蓝牙透传机制从TCP迁移到https
|
||||
*/
|
||||
@POST
|
||||
// @Url 一定要传入以“/”结尾注解会替换 baseUrl,每次调用可传入完整 URL
|
||||
suspend fun blePassThrough(@Url url: String, @Body body: RequestBody): Response<ResponseBody>
|
||||
|
||||
/**
|
||||
* 获取国家代码号
|
||||
*/
|
||||
@@ -1362,6 +1377,7 @@ interface INetworkService {
|
||||
@POST("map/setupRefreshLocation")
|
||||
suspend fun setupRefreshLocation(@Field("deviceId") deviceId: String): BaseResponse<String>
|
||||
|
||||
|
||||
/*******************************************以下是国内版本接口***********************************/
|
||||
/**
|
||||
* 获取微信登录的OpedId
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.abbidot.tracker.bean.SetMealBean
|
||||
import com.abbidot.tracker.bean.SubscriptionsOrderBean
|
||||
import com.abbidot.tracker.constant.ConstantInt
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
|
||||
|
||||
/**
|
||||
@@ -803,6 +804,12 @@ object NetworkApi : BaseNetworkApi<INetworkService>(INetworkService.BASE_URL) {
|
||||
service.getRechargeHistory(deviceId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 蓝牙透传机制从TCP迁移到https
|
||||
*/
|
||||
suspend fun blePassThrough(url: String, body: RequestBody) =
|
||||
service.blePassThrough(url, body)
|
||||
|
||||
/**
|
||||
* 取消自动续费/自动订阅
|
||||
*/
|
||||
|
||||
@@ -185,7 +185,7 @@ class HomeV2Activity : BaseActivity<ActivityHomeV2Binding>(ActivityHomeV2Binding
|
||||
// 当网络可用时调用
|
||||
super.onAvailable(network)
|
||||
LogUtil.e("当网络可用时,onAvailable")
|
||||
if (mNetworkRequestsFailRetryCount == mNetworkRequestsFailMaxCount && mPetList.size == 0) {
|
||||
if (mNetworkRequestsFailRetryCount == mNetworkRequestsFailMaxCount && mPetList.isEmpty()) {
|
||||
mNetworkRequestsFailRetryCount = 0
|
||||
isFirst = false
|
||||
mDataViewModel.getHomeBindPetList(this@HomeV2Activity)
|
||||
@@ -239,7 +239,9 @@ class HomeV2Activity : BaseActivity<ActivityHomeV2Binding>(ActivityHomeV2Binding
|
||||
}
|
||||
}
|
||||
|
||||
override fun liveDataObserve() {/*
|
||||
override fun liveDataObserve() {
|
||||
|
||||
/*
|
||||
Dispatchers.IO 在Android中主要用于执行输入输出操作,如文件读写和网络请求
|
||||
Dispatchers.Main:适用于处理与UI相关的操作,如更新UI界面、响应用户输入等。
|
||||
Dispatchers.Default:适用于执行CPU密集型任务,如算法计算、数据处理等
|
||||
@@ -358,6 +360,21 @@ class HomeV2Activity : BaseActivity<ActivityHomeV2Binding>(ActivityHomeV2Binding
|
||||
}
|
||||
}
|
||||
}
|
||||
//蓝牙透传
|
||||
XEventBus.observe(this, EventName.BlePassThrough) { receiveData: ReceiveDeviceData ->
|
||||
getPet(false)?.apply {
|
||||
receiveData.data?.let { d ->
|
||||
val sendData = d.sliceArray(8..d.size - 2)
|
||||
if (macID == receiveData.mac) {
|
||||
mDataViewModel.blePassThrough(this, sendData)
|
||||
} else {
|
||||
getMacPet(receiveData.mac)?.let { p ->
|
||||
mDataViewModel.blePassThrough(p, sendData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//蓝牙上报的设备日志
|
||||
mLogBleReportViewModel.apply {
|
||||
@@ -392,7 +409,7 @@ class HomeV2Activity : BaseActivity<ActivityHomeV2Binding>(ActivityHomeV2Binding
|
||||
override fun onResult(any: Any) {
|
||||
LogUtil.e("HomeV2Activity------>>获取所有绑定宠物数据")
|
||||
isRequestPetData = true
|
||||
if (mPetList.size > 0) {
|
||||
if (mPetList.isNotEmpty()) {
|
||||
it.getOrNull()?.apply {
|
||||
for (oPet in mPetList) {
|
||||
for (nPet in this) {
|
||||
@@ -597,7 +614,7 @@ class HomeV2Activity : BaseActivity<ActivityHomeV2Binding>(ActivityHomeV2Binding
|
||||
*/
|
||||
fun selectPetDialog(checkPackageExpire: Boolean = true) {
|
||||
isCheckPackageExpire = checkPackageExpire
|
||||
if (mPetList.size == 0) {
|
||||
if (mPetList.isEmpty()) {
|
||||
showToast(R.string.no_bind_pet)
|
||||
return
|
||||
}
|
||||
@@ -608,13 +625,29 @@ class HomeV2Activity : BaseActivity<ActivityHomeV2Binding>(ActivityHomeV2Binding
|
||||
}
|
||||
|
||||
fun getPet(showNoPetTip: Boolean = true): PetBean? {
|
||||
return if (mPetList.size > 0) mPetList[mSelectPetPosition]
|
||||
return if (mPetList.isNotEmpty()) mPetList[mSelectPetPosition]
|
||||
else {
|
||||
if (isRequestPetData && showNoPetTip) showToast(R.string.no_bind_pet)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定mac宠物
|
||||
*/
|
||||
fun getMacPet(mac: String): PetBean? {
|
||||
return if (mPetList.isNotEmpty()) {
|
||||
for (p in mPetList) {
|
||||
if (p.macID == mac) {
|
||||
return p
|
||||
}
|
||||
}
|
||||
null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转debug调试页面
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,6 @@ import com.abbidot.tracker.constant.ConstantString
|
||||
import com.abbidot.tracker.constant.GetResultCallback
|
||||
import com.abbidot.tracker.databinding.ActivityAddPairedSuccessBinding
|
||||
import com.abbidot.tracker.deprecated.ui.activity.vm.AddTrackerDeviceViewModel
|
||||
import com.abbidot.tracker.ui.activity.device.wifi.AddWifiPowerZone1Activity
|
||||
import com.abbidot.tracker.ui.activity.subscribe.SubscriptionPlanActivity
|
||||
import com.abbidot.tracker.util.Util
|
||||
import com.abbidot.tracker.util.ViewUtil
|
||||
@@ -238,9 +237,10 @@ class AddPairedSuccessActivity :
|
||||
if (isFirstBind) {
|
||||
mPetBean?.apply {
|
||||
val intent =
|
||||
Intent(mContext, AddWifiPowerZone1Activity::class.java)
|
||||
Intent(mContext, InitializingDeviceActivity::class.java)
|
||||
intent.putExtra(ConstantString.Pet, this)
|
||||
intent.putExtra(ConstantString.isFirstBind, isFirstBind)
|
||||
intent.putExtra(ConstantString.Type, mType)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.abbidot.tracker.ui.activity.device
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.activity.viewModels
|
||||
import com.abbidot.tracker.R
|
||||
import com.abbidot.tracker.base.BaseActivity
|
||||
import com.abbidot.tracker.bean.PetBean
|
||||
@@ -9,10 +10,12 @@ import com.abbidot.tracker.constant.ConstantString
|
||||
import com.abbidot.tracker.databinding.ActivityInitializingDeviceBinding
|
||||
import com.abbidot.tracker.ui.activity.device.wifi.AddWifiPowerZone1Activity
|
||||
import com.abbidot.tracker.util.Util
|
||||
import com.abbidot.tracker.vm.CountDownTimerViewModel
|
||||
|
||||
class InitializingDeviceActivity :
|
||||
BaseActivity<ActivityInitializingDeviceBinding>(ActivityInitializingDeviceBinding::inflate) {
|
||||
|
||||
private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels()
|
||||
private var mPetBean: PetBean? = null
|
||||
private var isFirstBind = false
|
||||
private var mDeviceType = ConstantInt.Type1
|
||||
@@ -43,6 +46,22 @@ class InitializingDeviceActivity :
|
||||
startActivityFinish(intent)
|
||||
}
|
||||
}
|
||||
mCountDownTimerViewModel.startCountDown(6)
|
||||
}
|
||||
|
||||
override fun liveDataObserve() {
|
||||
mCountDownTimerViewModel.apply {
|
||||
mCountDowningLiveData.observe(this@InitializingDeviceActivity) {
|
||||
val string = getString(R.string.txt_continue) + " (${it}s)"
|
||||
mViewBinding.btnPaySuccessInitDeviceNext.text = string
|
||||
}
|
||||
mCountDownEndLiveData.observe(this@InitializingDeviceActivity) {
|
||||
mViewBinding.btnPaySuccessInitDeviceNext.let { v ->
|
||||
v.setText(R.string.txt_continue)
|
||||
setButtonEnabled(v, ConstantInt.Type1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun leftBackOnClick() {
|
||||
|
||||
@@ -469,10 +469,10 @@ class SureSubscriptionPlanActivity :
|
||||
planTimeMonthsCount = cPlan.planTimeMonthsCount
|
||||
mealUnit = cPlan.mealUnit
|
||||
planCategory = cPlan.planCategory
|
||||
autoRenewPrice =
|
||||
Utils.formatDecimal(cPlan.autoRenewPrice + cPlan.autoRenewPrice * mTaxRate, 2)
|
||||
.toDouble()
|
||||
// autoRenewPrice = cPlan.autoRenewPrice
|
||||
// autoRenewPrice =
|
||||
// Utils.formatDecimal(cPlan.autoRenewPrice + cPlan.autoRenewPrice * mTaxRate, 2)
|
||||
// .toDouble()
|
||||
autoRenewPrice = cPlan.autoRenewPrice
|
||||
mealDesc =
|
||||
mViewBinding.ilSubscribePlanDetail.tvSureSubscribePlanMoney.text.toString() + mViewBinding.ilSubscribePlanDetail.tvSureSubscribePlanPer.text.toString()
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ class ActivityV2Fragment :
|
||||
*/
|
||||
fun showPetNameAndHead(position: Int) {
|
||||
getHomeV2Activity()?.let {
|
||||
if (it.mPetList.size == 0) {
|
||||
if (it.mPetList.isEmpty()) {
|
||||
return
|
||||
}
|
||||
mCurrentShowPetPos = position
|
||||
|
||||
@@ -38,6 +38,11 @@ import com.abbidot.tracker.widget.NoClickSlideSeekBar
|
||||
import com.abbidot.tracker.widget.TypefaceTextView
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
||||
/**
|
||||
@@ -482,7 +487,7 @@ class RouteV3Fragment : BaseFragment<FragmentRouteV3Binding>(FragmentRouteV3Bind
|
||||
*/
|
||||
fun showPetNameAndHead(position: Int) {
|
||||
getHomeV2Activity()?.apply {
|
||||
if (mPetList.size == 0) {
|
||||
if (mPetList.isEmpty()) {
|
||||
return
|
||||
}
|
||||
mCurrentShowPetPos = position
|
||||
@@ -503,7 +508,19 @@ class RouteV3Fragment : BaseFragment<FragmentRouteV3Binding>(FragmentRouteV3Bind
|
||||
|
||||
private fun setLatLngData(data: MutableList<HistoryDataBean>) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
mHistoryDataList = data
|
||||
|
||||
/*慢速步行:epsilon=0.5,插值 2 个
|
||||
日常骑行:epsilon=1.0,插值 2 个
|
||||
机动车行驶:epsilon=1.5~2.0,插值 3 个
|
||||
**/
|
||||
// 1. 降噪
|
||||
val filterPoints = filterNoisePoint(data)
|
||||
// 2. RDP平滑抽稀
|
||||
val smoothPoints = rdpSmooth(filterPoints, epsilon = 0.5)
|
||||
// 3. 插值加密线条
|
||||
val finalPoints = latLngInterpolate(smoothPoints, step = 2)
|
||||
|
||||
mHistoryDataList = finalPoints.toMutableList()
|
||||
getHomeV2Activity()?.getPet(false)?.let {
|
||||
mHistoryDataMapCommon.setPetBean(it)
|
||||
}
|
||||
@@ -517,17 +534,17 @@ class RouteV3Fragment : BaseFragment<FragmentRouteV3Binding>(FragmentRouteV3Bind
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data.size == 0) {
|
||||
if (finalPoints.isEmpty()) {
|
||||
setSeekBarShowHide(false)
|
||||
mHistoryDataMapCommon.setPetUserLatLng()
|
||||
return@launch
|
||||
}
|
||||
|
||||
setSeekBarShowHide(true)
|
||||
val max = data.size - 1
|
||||
val max = finalPoints.size - 1
|
||||
setSeekBarMax(max)
|
||||
|
||||
mHistoryDataMapCommon.setLatLngData(data)
|
||||
mHistoryDataMapCommon.setLatLngData(finalPoints.toMutableList())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,4 +716,134 @@ class RouteV3Fragment : BaseFragment<FragmentRouteV3Binding>(FragmentRouteV3Bind
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 两点间距离 单位米
|
||||
*/
|
||||
private fun distanceBetween(p1: HistoryDataBean, p2: HistoryDataBean): Double {
|
||||
val r = 6371000.0
|
||||
val lat1 = p1.latitude * Math.PI / 180
|
||||
val lng1 = p1.longitude * Math.PI / 180
|
||||
val lat2 = p2.latitude * Math.PI / 180
|
||||
val lng2 = p2.longitude * Math.PI / 180
|
||||
|
||||
val dLat = lat2 - lat1
|
||||
val dLng = lng2 - lng1
|
||||
val a = sin(dLat / 2).pow(2) + cos(lat1) * cos(lat2) * sin(dLng / 2).pow(2)
|
||||
val c = 2 * atan2(sqrt(a), sqrt(1 - a))
|
||||
return r * c
|
||||
}
|
||||
|
||||
/**
|
||||
* Ramer-Douglas-Peucker 轨迹抽稀平滑
|
||||
* @param epsilon 阈值 建议0.5~2.0
|
||||
*/
|
||||
private fun rdpSmooth(
|
||||
points: List<HistoryDataBean>, epsilon: Double = 1.0
|
||||
): List<HistoryDataBean> {
|
||||
if (points.size < 3) return points
|
||||
var maxDist = 0.0
|
||||
var index = 0
|
||||
val start = points.first()
|
||||
val end = points.last()
|
||||
|
||||
for (i in 1 until points.lastIndex) {
|
||||
val dist = pointToLineDist(p = points[i], a = start, b = end)
|
||||
if (dist > maxDist) {
|
||||
maxDist = dist
|
||||
index = i
|
||||
}
|
||||
}
|
||||
|
||||
return if (maxDist > epsilon) {
|
||||
val left = rdpSmooth(points.subList(0, index + 1), epsilon)
|
||||
val right = rdpSmooth(points.subList(index, points.size), epsilon)
|
||||
left.dropLast(1) + right
|
||||
} else {
|
||||
listOf(start, end)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点到线段距离
|
||||
*/
|
||||
private fun pointToLineDist(
|
||||
p: HistoryDataBean,
|
||||
a: HistoryDataBean,
|
||||
b: HistoryDataBean
|
||||
): Double {
|
||||
val ax = a.longitude
|
||||
val ay = a.latitude
|
||||
val bx = b.longitude
|
||||
val by = b.latitude
|
||||
val px = p.longitude
|
||||
val py = p.latitude
|
||||
|
||||
val cross = (px - ax) * (bx - ax) + (py - ay) * (by - ay)
|
||||
if (cross <= 0.0) return distanceBetween(p1 = p, p2 = a)
|
||||
|
||||
val len2 = (bx - ax).pow(2) + (by - ay).pow(2)
|
||||
if (cross >= len2) return distanceBetween(p1 = p, p2 = b)
|
||||
|
||||
val t = cross / len2
|
||||
val nearX = ax + t * (bx - ax)
|
||||
val nearY = ay + t * (by - ay)
|
||||
return distanceBetween(
|
||||
p1 = p,
|
||||
p2 = HistoryDataBean().apply {
|
||||
latitude = nearY
|
||||
longitude = nearX
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 相邻两点插值补点,让线条更顺滑
|
||||
* @param step 插值个数 推荐2~3
|
||||
*/
|
||||
private fun latLngInterpolate(
|
||||
points: List<HistoryDataBean>,
|
||||
step: Int = 2
|
||||
): List<HistoryDataBean> {
|
||||
if (points.size < 2) return points
|
||||
val result = mutableListOf<HistoryDataBean>()
|
||||
for (i in 0 until points.size - 1) {
|
||||
val p0 = points[i]
|
||||
val p1 = points[i + 1]
|
||||
result.add(p0)
|
||||
for (s in 1..step) {
|
||||
val ratio = s.toDouble() / (step + 1)
|
||||
val lat = p0.latitude + (p1.latitude - p0.latitude) * ratio
|
||||
val lng = p0.longitude + (p1.longitude - p0.longitude) * ratio
|
||||
result.add(HistoryDataBean().apply {
|
||||
latitude = lat
|
||||
longitude = lng
|
||||
})
|
||||
}
|
||||
}
|
||||
result.add(points.last())
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* GPS降噪,过滤跳点漂移
|
||||
*/
|
||||
private fun filterNoisePoint(
|
||||
data: List<HistoryDataBean>,
|
||||
minDis: Double = 5.0,
|
||||
maxDis: Double = 60.0
|
||||
): List<HistoryDataBean> {
|
||||
if (data.isEmpty()) return emptyList()
|
||||
val res = mutableListOf<HistoryDataBean>().apply { add(data.first()) }
|
||||
for (i in 1 until data.size) {
|
||||
val last = res.last()
|
||||
val d = data[i]
|
||||
val dis = distanceBetween(p1 = last, p2 = d)
|
||||
if (dis in minDis..maxDis) {
|
||||
res.add(d)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
@@ -63,12 +63,6 @@ import com.google.android.gms.tasks.CancellationTokenSource
|
||||
import com.google.android.gms.tasks.Task
|
||||
import com.qmuiteam.qmui.util.QMUIDisplayHelper
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
||||
/**
|
||||
@@ -838,21 +832,10 @@ abstract class BaseGoogleMapFragment :
|
||||
latLngList
|
||||
}
|
||||
|
||||
/*慢速步行:epsilon=0.5,插值 2 个
|
||||
日常骑行:epsilon=1.0,插值 2 个
|
||||
机动车行驶:epsilon=1.5~2.0,插值 3 个
|
||||
**/
|
||||
// 1. 降噪
|
||||
val filterPoints = filterNoisePoint(newLatLngList)
|
||||
// 2. RDP平滑抽稀
|
||||
val smoothPoints = rdpSmooth(filterPoints, epsilon = 0.5)
|
||||
// 3. 插值加密线条
|
||||
val finalPoints = latLngInterpolate(smoothPoints, step = 2)
|
||||
|
||||
//先黄黑线
|
||||
getPolylineOptions(8f, R.color.line_stroke_color).let {
|
||||
it.geodesic(true)
|
||||
it.addAll(finalPoints)
|
||||
it.addAll(newLatLngList)
|
||||
googleMap.addPolyline(it).apply {
|
||||
startCap = RoundCap()
|
||||
endCap = RoundCap()
|
||||
@@ -868,7 +851,7 @@ abstract class BaseGoogleMapFragment :
|
||||
//再画黄线
|
||||
getPolylineOptions(5f, R.color.rote_line_color).let {
|
||||
it.geodesic(true)
|
||||
it.addAll(finalPoints)
|
||||
it.addAll(newLatLngList)
|
||||
googleMap.addPolyline(it).apply {
|
||||
startCap = RoundCap()
|
||||
endCap = RoundCap()
|
||||
@@ -1300,112 +1283,112 @@ abstract class BaseGoogleMapFragment :
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 两点间距离 单位米
|
||||
*/
|
||||
private fun distanceBetween(p1: LatLng, p2: LatLng): Double {
|
||||
val r = 6371000.0
|
||||
val lat1 = p1.latitude * PI / 180
|
||||
val lng1 = p1.longitude * PI / 180
|
||||
val lat2 = p2.latitude * PI / 180
|
||||
val lng2 = p2.longitude * PI / 180
|
||||
|
||||
val dLat = lat2 - lat1
|
||||
val dLng = lng2 - lng1
|
||||
val a = sin(dLat / 2).pow(2) + cos(lat1) * cos(lat2) * sin(dLng / 2).pow(2)
|
||||
val c = 2 * atan2(sqrt(a), sqrt(1 - a))
|
||||
return r * c
|
||||
}
|
||||
|
||||
/**
|
||||
* Ramer-Douglas-Peucker 轨迹抽稀平滑
|
||||
* @param epsilon 阈值 建议0.5~2.0
|
||||
*/
|
||||
private fun rdpSmooth(points: List<LatLng>, epsilon: Double = 1.0): List<LatLng> {
|
||||
if (points.size < 3) return points
|
||||
var maxDist = 0.0
|
||||
var index = 0
|
||||
val start = points.first()
|
||||
val end = points.last()
|
||||
|
||||
for (i in 1 until points.lastIndex) {
|
||||
val dist = pointToLineDist(points[i], start, end)
|
||||
if (dist > maxDist) {
|
||||
maxDist = dist
|
||||
index = i
|
||||
}
|
||||
}
|
||||
|
||||
return if (maxDist > epsilon) {
|
||||
val left = rdpSmooth(points.subList(0, index + 1), epsilon)
|
||||
val right = rdpSmooth(points.subList(index, points.size), epsilon)
|
||||
left.dropLast(1) + right
|
||||
} else {
|
||||
listOf(start, end)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点到线段距离
|
||||
*/
|
||||
private fun pointToLineDist(p: LatLng, a: LatLng, b: LatLng): Double {
|
||||
val ax = a.longitude
|
||||
val ay = a.latitude
|
||||
val bx = b.longitude
|
||||
val by = b.latitude
|
||||
val px = p.longitude
|
||||
val py = p.latitude
|
||||
|
||||
val cross = (px - ax) * (bx - ax) + (py - ay) * (by - ay)
|
||||
if (cross <= 0.0) return distanceBetween(p, a)
|
||||
|
||||
val len2 = (bx - ax).pow(2) + (by - ay).pow(2)
|
||||
if (cross >= len2) return distanceBetween(p, b)
|
||||
|
||||
val t = cross / len2
|
||||
val nearX = ax + t * (bx - ax)
|
||||
val nearY = ay + t * (by - ay)
|
||||
return distanceBetween(p, LatLng(nearY, nearX))
|
||||
}
|
||||
|
||||
/**
|
||||
* 相邻两点插值补点,让线条更顺滑
|
||||
* @param step 插值个数 推荐2~3
|
||||
*/
|
||||
private fun latLngInterpolate(points: List<LatLng>, step: Int = 2): List<LatLng> {
|
||||
if (points.size < 2) return points
|
||||
val result = mutableListOf<LatLng>()
|
||||
for (i in 0 until points.size - 1) {
|
||||
val p0 = points[i]
|
||||
val p1 = points[i + 1]
|
||||
result.add(p0)
|
||||
for (s in 1..step) {
|
||||
val ratio = s.toDouble() / (step + 1)
|
||||
val lat = p0.latitude + (p1.latitude - p0.latitude) * ratio
|
||||
val lng = p0.longitude + (p1.longitude - p0.longitude) * ratio
|
||||
result.add(LatLng(lat, lng))
|
||||
}
|
||||
}
|
||||
result.add(points.last())
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* GPS降噪,过滤跳点漂移
|
||||
*/
|
||||
private fun filterNoisePoint(
|
||||
points: List<LatLng>, minDis: Double = 5.0, maxDis: Double = 60.0
|
||||
): List<LatLng> {
|
||||
if (points.isEmpty()) return emptyList()
|
||||
val res = mutableListOf<LatLng>().apply { add(points.first()) }
|
||||
for (i in 1 until points.size) {
|
||||
val last = res.last()
|
||||
val dis = distanceBetween(last, points[i])
|
||||
if (dis in minDis..maxDis) {
|
||||
res.add(points[i])
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
// /**
|
||||
// * 两点间距离 单位米
|
||||
// */
|
||||
// private fun distanceBetween(p1: LatLng, p2: LatLng): Double {
|
||||
// val r = 6371000.0
|
||||
// val lat1 = p1.latitude * PI / 180
|
||||
// val lng1 = p1.longitude * PI / 180
|
||||
// val lat2 = p2.latitude * PI / 180
|
||||
// val lng2 = p2.longitude * PI / 180
|
||||
//
|
||||
// val dLat = lat2 - lat1
|
||||
// val dLng = lng2 - lng1
|
||||
// val a = sin(dLat / 2).pow(2) + cos(lat1) * cos(lat2) * sin(dLng / 2).pow(2)
|
||||
// val c = 2 * atan2(sqrt(a), sqrt(1 - a))
|
||||
// return r * c
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Ramer-Douglas-Peucker 轨迹抽稀平滑
|
||||
// * @param epsilon 阈值 建议0.5~2.0
|
||||
// */
|
||||
// private fun rdpSmooth(points: List<LatLng>, epsilon: Double = 1.0): List<LatLng> {
|
||||
// if (points.size < 3) return points
|
||||
// var maxDist = 0.0
|
||||
// var index = 0
|
||||
// val start = points.first()
|
||||
// val end = points.last()
|
||||
//
|
||||
// for (i in 1 until points.lastIndex) {
|
||||
// val dist = pointToLineDist(points[i], start, end)
|
||||
// if (dist > maxDist) {
|
||||
// maxDist = dist
|
||||
// index = i
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return if (maxDist > epsilon) {
|
||||
// val left = rdpSmooth(points.subList(0, index + 1), epsilon)
|
||||
// val right = rdpSmooth(points.subList(index, points.size), epsilon)
|
||||
// left.dropLast(1) + right
|
||||
// } else {
|
||||
// listOf(start, end)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 点到线段距离
|
||||
// */
|
||||
// private fun pointToLineDist(p: LatLng, a: LatLng, b: LatLng): Double {
|
||||
// val ax = a.longitude
|
||||
// val ay = a.latitude
|
||||
// val bx = b.longitude
|
||||
// val by = b.latitude
|
||||
// val px = p.longitude
|
||||
// val py = p.latitude
|
||||
//
|
||||
// val cross = (px - ax) * (bx - ax) + (py - ay) * (by - ay)
|
||||
// if (cross <= 0.0) return distanceBetween(p, a)
|
||||
//
|
||||
// val len2 = (bx - ax).pow(2) + (by - ay).pow(2)
|
||||
// if (cross >= len2) return distanceBetween(p, b)
|
||||
//
|
||||
// val t = cross / len2
|
||||
// val nearX = ax + t * (bx - ax)
|
||||
// val nearY = ay + t * (by - ay)
|
||||
// return distanceBetween(p, LatLng(nearY, nearX))
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 相邻两点插值补点,让线条更顺滑
|
||||
// * @param step 插值个数 推荐2~3
|
||||
// */
|
||||
// private fun latLngInterpolate(points: List<LatLng>, step: Int = 2): List<LatLng> {
|
||||
// if (points.size < 2) return points
|
||||
// val result = mutableListOf<LatLng>()
|
||||
// for (i in 0 until points.size - 1) {
|
||||
// val p0 = points[i]
|
||||
// val p1 = points[i + 1]
|
||||
// result.add(p0)
|
||||
// for (s in 1..step) {
|
||||
// val ratio = s.toDouble() / (step + 1)
|
||||
// val lat = p0.latitude + (p1.latitude - p0.latitude) * ratio
|
||||
// val lng = p0.longitude + (p1.longitude - p0.longitude) * ratio
|
||||
// result.add(LatLng(lat, lng))
|
||||
// }
|
||||
// }
|
||||
// result.add(points.last())
|
||||
// return result
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * GPS降噪,过滤跳点漂移
|
||||
// */
|
||||
// private fun filterNoisePoint(
|
||||
// points: List<LatLng>, minDis: Double = 5.0, maxDis: Double = 60.0
|
||||
// ): List<LatLng> {
|
||||
// if (points.isEmpty()) return emptyList()
|
||||
// val res = mutableListOf<LatLng>().apply { add(points.first()) }
|
||||
// for (i in 1 until points.size) {
|
||||
// val last = res.last()
|
||||
// val dis = distanceBetween(last, points[i])
|
||||
// if (dis in minDis..maxDis) {
|
||||
// res.add(points[i])
|
||||
// }
|
||||
// }
|
||||
// return res
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -119,12 +119,13 @@ class PetV2Fragment : BaseFragment<FragmentPetV2Binding>(FragmentPetV2Binding::i
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
mNotificationsViewModel.getMessageUnReadCount()
|
||||
(mFragments[1] as HomeTrackFragment).onResume()
|
||||
// (mFragments[1] as HomeTrackFragment).onResume()
|
||||
getHomeV2Activity()?.apply {
|
||||
//其他页面是否选择了宠物
|
||||
if (mCurrentShowPetPos != mSelectPetPosition) {
|
||||
showPetNameAndHead(mSelectPetPosition)
|
||||
} else {
|
||||
(mFragments[0] as HomePetFragment).getPetAllInfoData()
|
||||
(mFragments[1] as HomeTrackFragment).getPetTrackerInfoData()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,6 +375,9 @@ class SRBleCmdUtil private constructor() {
|
||||
return getCrc8Cmd(byteArray)
|
||||
}
|
||||
|
||||
/**
|
||||
* 蓝牙透传上报设备信息
|
||||
*/
|
||||
fun setProxy(result: Int, data: ByteArray?): ByteArray {
|
||||
if (null == data) {
|
||||
return getCrc8Cmd(byteArrayOf(0x24, result.toByte(), 0))
|
||||
|
||||
@@ -242,10 +242,8 @@ class SRBleUtil private constructor() {
|
||||
XEventBus.post(EventName.BleGetLocation, bleDevice.mac)
|
||||
} else if (data0 == 0x24) {
|
||||
LogUtil.e("${bleDevice.mac},蓝牙透传消息")
|
||||
if (null == mSocketUtilManage) mSocketUtilManage = SocketUtilManageV2()
|
||||
mContext?.let {
|
||||
mSocketUtilManage?.initEasySocket(it, bleDevice.mac, data)
|
||||
}
|
||||
val deviceData = ReceiveDeviceData(bleDevice, data, bleDevice.mac)
|
||||
XEventBus.post(EventName.BlePassThrough, deviceData)
|
||||
} else if (data0 == 0x09 && data1 == 0x00) {
|
||||
val deviceData = ReceiveDeviceData(bleDevice, data, bleDevice.mac)
|
||||
if (isNotifyFirmware) {
|
||||
|
||||
@@ -4,12 +4,22 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.abbidot.baselibrary.constant.MMKVKey
|
||||
import com.abbidot.baselibrary.util.LogUtil
|
||||
import com.abbidot.baselibrary.util.MMKVUtil
|
||||
import com.abbidot.tracker.base.BaseActivity
|
||||
import com.abbidot.tracker.bean.HomeDataBean
|
||||
import com.abbidot.tracker.bean.PetBean
|
||||
import com.abbidot.tracker.retrofit2.INetworkService
|
||||
import com.abbidot.tracker.retrofit2.NetworkApi
|
||||
import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil
|
||||
import com.abbidot.tracker.util.bluetooth.SRBleUtil
|
||||
import com.mapbox.core.utils.TextUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
/**
|
||||
*Created by .yzq on 2022/3/11/011.
|
||||
@@ -18,6 +28,8 @@ import kotlinx.coroutines.launch
|
||||
*/
|
||||
class DataViewModel : ViewModel() {
|
||||
|
||||
private val mMutex = Mutex()
|
||||
|
||||
val mHomePetLiveData = MutableLiveData<Result<MutableList<PetBean>>>()
|
||||
val mHomeDataLiveData = MutableLiveData<Result<HomeDataBean>>()
|
||||
|
||||
@@ -50,4 +62,44 @@ class DataViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun blePassThrough(petBean: PetBean, binaryData: ByteArray) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
mMutex.withLock {
|
||||
try {
|
||||
var baseUrl =
|
||||
if (TextUtils.isEmpty(petBean.serverDomain)) INetworkService.BASE_URL_BLE
|
||||
else petBean.serverDomain
|
||||
if (!baseUrl.startsWith("https")) baseUrl = "https://$baseUrl"
|
||||
if (!baseUrl.endsWith("/")) baseUrl = "$baseUrl/"
|
||||
val requestBody =
|
||||
binaryData.toRequestBody("application/octet-stream".toMediaType())
|
||||
val response =
|
||||
NetworkApi.blePassThrough("${baseUrl}BlePassThrough", requestBody)
|
||||
if (response.isSuccessful) {
|
||||
// 读取响应二进制
|
||||
val b = response.body()
|
||||
if (null == b) {
|
||||
SRBleUtil.instance.isConnectBleSendCmdData(
|
||||
petBean.macID, SRBleCmdUtil.instance.setProxy(1, null)
|
||||
)
|
||||
} else {
|
||||
val result = b.bytes()
|
||||
SRBleUtil.instance.isConnectBleSendCmdData(
|
||||
petBean.macID, SRBleCmdUtil.instance.setProxy(0, result)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
SRBleUtil.instance.isConnectBleSendCmdData(
|
||||
petBean.macID, SRBleCmdUtil.instance.setProxy(1, null)
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
SRBleUtil.instance.isConnectBleSendCmdData(
|
||||
petBean.macID, SRBleCmdUtil.instance.setProxy(1, null)
|
||||
)
|
||||
LogUtil.e("蓝牙透传解析异常:${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@
|
||||
<com.abbidot.tracker.widget.TypefaceButton
|
||||
android:id="@+id/btn_pay_success_init_device_next"
|
||||
style="@style/my_match_RoundRect_Button_style"
|
||||
android:enabled="false"
|
||||
android:text="@string/txt_continue"
|
||||
app:qmui_radius="@dimen/dp_64"
|
||||
app:typeface="@string/roboto_bold_font" />
|
||||
|
||||
@@ -42,6 +42,7 @@ import androidx.annotation.StringDef
|
||||
EventName.BleGetLocation,
|
||||
EventName.BleReportData,
|
||||
EventName.LogReport,
|
||||
EventName.BlePassThrough,
|
||||
EventName.ActionConDeviceState
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@@ -112,5 +113,7 @@ annotation class EventName {
|
||||
|
||||
//设备固件版本
|
||||
const val BleFirmwareVersion = "firmwareVersion"
|
||||
//蓝牙透传
|
||||
const val BlePassThrough = "blePassThrough"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ abstract class BaseNetworkApi<I>(private val baseUrl: String) : IService<I> {
|
||||
getRetrofit().create(getServiceClass())
|
||||
}
|
||||
|
||||
protected open fun getRetrofit(): Retrofit {
|
||||
protected fun getRetrofit(): Retrofit {
|
||||
//添加gson解析null拦截器
|
||||
val gson = GsonBuilder().registerTypeAdapter(String::class.java, StringNullAdapter())
|
||||
.registerTypeAdapter(Double::class.java, DoubleDefault0Adapter()).create()
|
||||
@@ -37,6 +37,19 @@ abstract class BaseNetworkApi<I>(private val baseUrl: String) : IService<I> {
|
||||
.addConverterFactory(GsonConverterFactory.create(gson)).build()
|
||||
}
|
||||
|
||||
/**
|
||||
* 二进制上传专用 独立 Retrofit 配置
|
||||
*/
|
||||
// protected val binaryService: I by lazy {
|
||||
// binaryRetrofit().create(getServiceClass())
|
||||
// }
|
||||
|
||||
// 无 Gson 解析,只处理原始流/二进制
|
||||
// private fun binaryRetrofit(): Retrofit {
|
||||
// return Retrofit.Builder().baseUrl(baseUrl).client(getOkHttpClient())
|
||||
// .build()
|
||||
// }
|
||||
|
||||
private fun getServiceClass(): Class<I> {
|
||||
val genType = javaClass.genericSuperclass as ParameterizedType
|
||||
return genType.actualTypeArguments[0] as Class<I>
|
||||
|
||||
@@ -18,7 +18,8 @@ class AppUtils {
|
||||
|
||||
companion object {
|
||||
|
||||
private var isDebug: Boolean? = true
|
||||
// private var isDebug: Boolean? = true
|
||||
private var isDebug: Boolean? = null
|
||||
|
||||
/**
|
||||
* 屏幕密度,系统源码注释不推荐使用
|
||||
@@ -93,7 +94,7 @@ class AppUtils {
|
||||
* 是否是国内
|
||||
*/
|
||||
fun isChina(type: String = SWITCH_PAGE_TYPE): Boolean {
|
||||
return true
|
||||
return false
|
||||
if (isDebug()) {
|
||||
if (type == SWITCH_MAP_TYPE && MMKVUtil.getBoolean(MMKVKey.OnlyGoogleMap, false)) {
|
||||
//只启用谷歌地图
|
||||
|
||||
Reference in New Issue
Block a user