diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + - + + + - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt new file mode 100644 index 0000000..cb14054 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt @@ -0,0 +1,102 @@ +package com.casic.smarttube.widgets + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import com.amap.api.maps.Projection +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.MarkerOptions +import com.casic.smarttube.R +import com.pengxh.kt.lite.extensions.toBitmap +import java.util.* + +class GaoDeClusterMarkerView( + private val context: Context, firstMarkers: MarkerOptions, + projection: Projection, gridSize: Int +) { + //当前可观区域里的 聚合过之后的集合 + private val includeMarkers: ArrayList + + // 创建区域 + val bounds: LatLngBounds + var options: MarkerOptions = MarkerOptions() + + init { + val screenLocation = projection.toScreenLocation(firstMarkers.position) + //范围类 + val southwestPoint = Point(screenLocation.x - gridSize, screenLocation.y + gridSize) + //范围类 + val northeastPoint = Point(screenLocation.x + gridSize, screenLocation.y - gridSize) + bounds = LatLngBounds( + projection.fromScreenLocation(southwestPoint), + projection.fromScreenLocation(northeastPoint) + ) + //设置初始化marker属性 + options.anchor(0.5f, 1.3f) + .title(firstMarkers.title) + .position(firstMarkers.position) + .icon(firstMarkers.icon) + .snippet(firstMarkers.snippet) + .draggable(false) + includeMarkers = ArrayList() + includeMarkers.add(firstMarkers) + } + + /** + * 添加marker + */ + fun addMarker(markerOptions: MarkerOptions) { + includeMarkers.add(markerOptions) // 添加到列表中 + } + + /** + * 设置聚合点的中心位置以及图标 + */ + fun setPositionAndIcon() { + val size = includeMarkers.size + var lat = 0.0 + var lng = 0.0 + // 一个的时候 + if (size == 1) { //设置marker单个属性 + // 设置marker位置 + options.position( + LatLng( + includeMarkers[0].position.latitude, + includeMarkers[0].position.longitude + ) + ) + } else { // 聚合的时候 + //设置marker聚合属性 + for (op in includeMarkers) { + lat += op.position.latitude + lng += op.position.longitude + } + // 设置marker的位置为中心位置为聚集点的平均位置 + options.position(LatLng(lat / size, lng / size)) + } + options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap(size))) + } + + /** + * marker视图 + */ + private fun getBitmap(num: Int): Bitmap? { + val view = LayoutInflater.from(context).inflate(R.layout.marker_gaode, null) + val wellCountView = view.findViewById(R.id.wellCountView) + return if (num > 1) { + wellCountView.visibility = View.VISIBLE + wellCountView.text = String.format("${num}个") + wellCountView.gravity = Gravity.CENTER + view.toBitmap() + } else { + wellCountView.visibility = View.GONE + BitmapDescriptorFactory.fromResource(R.mipmap.well_location).bitmap + } + } +} \ No newline at end of file diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt new file mode 100644 index 0000000..cb14054 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt @@ -0,0 +1,102 @@ +package com.casic.smarttube.widgets + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import com.amap.api.maps.Projection +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.MarkerOptions +import com.casic.smarttube.R +import com.pengxh.kt.lite.extensions.toBitmap +import java.util.* + +class GaoDeClusterMarkerView( + private val context: Context, firstMarkers: MarkerOptions, + projection: Projection, gridSize: Int +) { + //当前可观区域里的 聚合过之后的集合 + private val includeMarkers: ArrayList + + // 创建区域 + val bounds: LatLngBounds + var options: MarkerOptions = MarkerOptions() + + init { + val screenLocation = projection.toScreenLocation(firstMarkers.position) + //范围类 + val southwestPoint = Point(screenLocation.x - gridSize, screenLocation.y + gridSize) + //范围类 + val northeastPoint = Point(screenLocation.x + gridSize, screenLocation.y - gridSize) + bounds = LatLngBounds( + projection.fromScreenLocation(southwestPoint), + projection.fromScreenLocation(northeastPoint) + ) + //设置初始化marker属性 + options.anchor(0.5f, 1.3f) + .title(firstMarkers.title) + .position(firstMarkers.position) + .icon(firstMarkers.icon) + .snippet(firstMarkers.snippet) + .draggable(false) + includeMarkers = ArrayList() + includeMarkers.add(firstMarkers) + } + + /** + * 添加marker + */ + fun addMarker(markerOptions: MarkerOptions) { + includeMarkers.add(markerOptions) // 添加到列表中 + } + + /** + * 设置聚合点的中心位置以及图标 + */ + fun setPositionAndIcon() { + val size = includeMarkers.size + var lat = 0.0 + var lng = 0.0 + // 一个的时候 + if (size == 1) { //设置marker单个属性 + // 设置marker位置 + options.position( + LatLng( + includeMarkers[0].position.latitude, + includeMarkers[0].position.longitude + ) + ) + } else { // 聚合的时候 + //设置marker聚合属性 + for (op in includeMarkers) { + lat += op.position.latitude + lng += op.position.longitude + } + // 设置marker的位置为中心位置为聚集点的平均位置 + options.position(LatLng(lat / size, lng / size)) + } + options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap(size))) + } + + /** + * marker视图 + */ + private fun getBitmap(num: Int): Bitmap? { + val view = LayoutInflater.from(context).inflate(R.layout.marker_gaode, null) + val wellCountView = view.findViewById(R.id.wellCountView) + return if (num > 1) { + wellCountView.visibility = View.VISIBLE + wellCountView.text = String.format("${num}个") + wellCountView.gravity = Gravity.CENTER + view.toBitmap() + } else { + wellCountView.visibility = View.GONE + BitmapDescriptorFactory.fromResource(R.mipmap.well_location).bitmap + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml new file mode 100644 index 0000000..e329b7d --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt new file mode 100644 index 0000000..cb14054 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt @@ -0,0 +1,102 @@ +package com.casic.smarttube.widgets + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import com.amap.api.maps.Projection +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.MarkerOptions +import com.casic.smarttube.R +import com.pengxh.kt.lite.extensions.toBitmap +import java.util.* + +class GaoDeClusterMarkerView( + private val context: Context, firstMarkers: MarkerOptions, + projection: Projection, gridSize: Int +) { + //当前可观区域里的 聚合过之后的集合 + private val includeMarkers: ArrayList + + // 创建区域 + val bounds: LatLngBounds + var options: MarkerOptions = MarkerOptions() + + init { + val screenLocation = projection.toScreenLocation(firstMarkers.position) + //范围类 + val southwestPoint = Point(screenLocation.x - gridSize, screenLocation.y + gridSize) + //范围类 + val northeastPoint = Point(screenLocation.x + gridSize, screenLocation.y - gridSize) + bounds = LatLngBounds( + projection.fromScreenLocation(southwestPoint), + projection.fromScreenLocation(northeastPoint) + ) + //设置初始化marker属性 + options.anchor(0.5f, 1.3f) + .title(firstMarkers.title) + .position(firstMarkers.position) + .icon(firstMarkers.icon) + .snippet(firstMarkers.snippet) + .draggable(false) + includeMarkers = ArrayList() + includeMarkers.add(firstMarkers) + } + + /** + * 添加marker + */ + fun addMarker(markerOptions: MarkerOptions) { + includeMarkers.add(markerOptions) // 添加到列表中 + } + + /** + * 设置聚合点的中心位置以及图标 + */ + fun setPositionAndIcon() { + val size = includeMarkers.size + var lat = 0.0 + var lng = 0.0 + // 一个的时候 + if (size == 1) { //设置marker单个属性 + // 设置marker位置 + options.position( + LatLng( + includeMarkers[0].position.latitude, + includeMarkers[0].position.longitude + ) + ) + } else { // 聚合的时候 + //设置marker聚合属性 + for (op in includeMarkers) { + lat += op.position.latitude + lng += op.position.longitude + } + // 设置marker的位置为中心位置为聚集点的平均位置 + options.position(LatLng(lat / size, lng / size)) + } + options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap(size))) + } + + /** + * marker视图 + */ + private fun getBitmap(num: Int): Bitmap? { + val view = LayoutInflater.from(context).inflate(R.layout.marker_gaode, null) + val wellCountView = view.findViewById(R.id.wellCountView) + return if (num > 1) { + wellCountView.visibility = View.VISIBLE + wellCountView.text = String.format("${num}个") + wellCountView.gravity = Gravity.CENTER + view.toBitmap() + } else { + wellCountView.visibility = View.GONE + BitmapDescriptorFactory.fromResource(R.mipmap.well_location).bitmap + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml new file mode 100644 index 0000000..e329b7d --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml new file mode 100644 index 0000000..15e6a4b --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt new file mode 100644 index 0000000..cb14054 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt @@ -0,0 +1,102 @@ +package com.casic.smarttube.widgets + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import com.amap.api.maps.Projection +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.MarkerOptions +import com.casic.smarttube.R +import com.pengxh.kt.lite.extensions.toBitmap +import java.util.* + +class GaoDeClusterMarkerView( + private val context: Context, firstMarkers: MarkerOptions, + projection: Projection, gridSize: Int +) { + //当前可观区域里的 聚合过之后的集合 + private val includeMarkers: ArrayList + + // 创建区域 + val bounds: LatLngBounds + var options: MarkerOptions = MarkerOptions() + + init { + val screenLocation = projection.toScreenLocation(firstMarkers.position) + //范围类 + val southwestPoint = Point(screenLocation.x - gridSize, screenLocation.y + gridSize) + //范围类 + val northeastPoint = Point(screenLocation.x + gridSize, screenLocation.y - gridSize) + bounds = LatLngBounds( + projection.fromScreenLocation(southwestPoint), + projection.fromScreenLocation(northeastPoint) + ) + //设置初始化marker属性 + options.anchor(0.5f, 1.3f) + .title(firstMarkers.title) + .position(firstMarkers.position) + .icon(firstMarkers.icon) + .snippet(firstMarkers.snippet) + .draggable(false) + includeMarkers = ArrayList() + includeMarkers.add(firstMarkers) + } + + /** + * 添加marker + */ + fun addMarker(markerOptions: MarkerOptions) { + includeMarkers.add(markerOptions) // 添加到列表中 + } + + /** + * 设置聚合点的中心位置以及图标 + */ + fun setPositionAndIcon() { + val size = includeMarkers.size + var lat = 0.0 + var lng = 0.0 + // 一个的时候 + if (size == 1) { //设置marker单个属性 + // 设置marker位置 + options.position( + LatLng( + includeMarkers[0].position.latitude, + includeMarkers[0].position.longitude + ) + ) + } else { // 聚合的时候 + //设置marker聚合属性 + for (op in includeMarkers) { + lat += op.position.latitude + lng += op.position.longitude + } + // 设置marker的位置为中心位置为聚集点的平均位置 + options.position(LatLng(lat / size, lng / size)) + } + options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap(size))) + } + + /** + * marker视图 + */ + private fun getBitmap(num: Int): Bitmap? { + val view = LayoutInflater.from(context).inflate(R.layout.marker_gaode, null) + val wellCountView = view.findViewById(R.id.wellCountView) + return if (num > 1) { + wellCountView.visibility = View.VISIBLE + wellCountView.text = String.format("${num}个") + wellCountView.gravity = Gravity.CENTER + view.toBitmap() + } else { + wellCountView.visibility = View.GONE + BitmapDescriptorFactory.fromResource(R.mipmap.well_location).bitmap + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml new file mode 100644 index 0000000..e329b7d --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml new file mode 100644 index 0000000..15e6a4b --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml new file mode 100644 index 0000000..0c2abfe --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt new file mode 100644 index 0000000..cb14054 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt @@ -0,0 +1,102 @@ +package com.casic.smarttube.widgets + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import com.amap.api.maps.Projection +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.MarkerOptions +import com.casic.smarttube.R +import com.pengxh.kt.lite.extensions.toBitmap +import java.util.* + +class GaoDeClusterMarkerView( + private val context: Context, firstMarkers: MarkerOptions, + projection: Projection, gridSize: Int +) { + //当前可观区域里的 聚合过之后的集合 + private val includeMarkers: ArrayList + + // 创建区域 + val bounds: LatLngBounds + var options: MarkerOptions = MarkerOptions() + + init { + val screenLocation = projection.toScreenLocation(firstMarkers.position) + //范围类 + val southwestPoint = Point(screenLocation.x - gridSize, screenLocation.y + gridSize) + //范围类 + val northeastPoint = Point(screenLocation.x + gridSize, screenLocation.y - gridSize) + bounds = LatLngBounds( + projection.fromScreenLocation(southwestPoint), + projection.fromScreenLocation(northeastPoint) + ) + //设置初始化marker属性 + options.anchor(0.5f, 1.3f) + .title(firstMarkers.title) + .position(firstMarkers.position) + .icon(firstMarkers.icon) + .snippet(firstMarkers.snippet) + .draggable(false) + includeMarkers = ArrayList() + includeMarkers.add(firstMarkers) + } + + /** + * 添加marker + */ + fun addMarker(markerOptions: MarkerOptions) { + includeMarkers.add(markerOptions) // 添加到列表中 + } + + /** + * 设置聚合点的中心位置以及图标 + */ + fun setPositionAndIcon() { + val size = includeMarkers.size + var lat = 0.0 + var lng = 0.0 + // 一个的时候 + if (size == 1) { //设置marker单个属性 + // 设置marker位置 + options.position( + LatLng( + includeMarkers[0].position.latitude, + includeMarkers[0].position.longitude + ) + ) + } else { // 聚合的时候 + //设置marker聚合属性 + for (op in includeMarkers) { + lat += op.position.latitude + lng += op.position.longitude + } + // 设置marker的位置为中心位置为聚集点的平均位置 + options.position(LatLng(lat / size, lng / size)) + } + options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap(size))) + } + + /** + * marker视图 + */ + private fun getBitmap(num: Int): Bitmap? { + val view = LayoutInflater.from(context).inflate(R.layout.marker_gaode, null) + val wellCountView = view.findViewById(R.id.wellCountView) + return if (num > 1) { + wellCountView.visibility = View.VISIBLE + wellCountView.text = String.format("${num}个") + wellCountView.gravity = Gravity.CENTER + view.toBitmap() + } else { + wellCountView.visibility = View.GONE + BitmapDescriptorFactory.fromResource(R.mipmap.well_location).bitmap + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml new file mode 100644 index 0000000..e329b7d --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml new file mode 100644 index 0000000..15e6a4b --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml new file mode 100644 index 0000000..0c2abfe --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_short_line.xml b/app/src/main/res/drawable/bottom_short_line.xml new file mode 100644 index 0000000..10c9062 --- /dev/null +++ b/app/src/main/res/drawable/bottom_short_line.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt new file mode 100644 index 0000000..cb14054 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt @@ -0,0 +1,102 @@ +package com.casic.smarttube.widgets + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import com.amap.api.maps.Projection +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.MarkerOptions +import com.casic.smarttube.R +import com.pengxh.kt.lite.extensions.toBitmap +import java.util.* + +class GaoDeClusterMarkerView( + private val context: Context, firstMarkers: MarkerOptions, + projection: Projection, gridSize: Int +) { + //当前可观区域里的 聚合过之后的集合 + private val includeMarkers: ArrayList + + // 创建区域 + val bounds: LatLngBounds + var options: MarkerOptions = MarkerOptions() + + init { + val screenLocation = projection.toScreenLocation(firstMarkers.position) + //范围类 + val southwestPoint = Point(screenLocation.x - gridSize, screenLocation.y + gridSize) + //范围类 + val northeastPoint = Point(screenLocation.x + gridSize, screenLocation.y - gridSize) + bounds = LatLngBounds( + projection.fromScreenLocation(southwestPoint), + projection.fromScreenLocation(northeastPoint) + ) + //设置初始化marker属性 + options.anchor(0.5f, 1.3f) + .title(firstMarkers.title) + .position(firstMarkers.position) + .icon(firstMarkers.icon) + .snippet(firstMarkers.snippet) + .draggable(false) + includeMarkers = ArrayList() + includeMarkers.add(firstMarkers) + } + + /** + * 添加marker + */ + fun addMarker(markerOptions: MarkerOptions) { + includeMarkers.add(markerOptions) // 添加到列表中 + } + + /** + * 设置聚合点的中心位置以及图标 + */ + fun setPositionAndIcon() { + val size = includeMarkers.size + var lat = 0.0 + var lng = 0.0 + // 一个的时候 + if (size == 1) { //设置marker单个属性 + // 设置marker位置 + options.position( + LatLng( + includeMarkers[0].position.latitude, + includeMarkers[0].position.longitude + ) + ) + } else { // 聚合的时候 + //设置marker聚合属性 + for (op in includeMarkers) { + lat += op.position.latitude + lng += op.position.longitude + } + // 设置marker的位置为中心位置为聚集点的平均位置 + options.position(LatLng(lat / size, lng / size)) + } + options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap(size))) + } + + /** + * marker视图 + */ + private fun getBitmap(num: Int): Bitmap? { + val view = LayoutInflater.from(context).inflate(R.layout.marker_gaode, null) + val wellCountView = view.findViewById(R.id.wellCountView) + return if (num > 1) { + wellCountView.visibility = View.VISIBLE + wellCountView.text = String.format("${num}个") + wellCountView.gravity = Gravity.CENTER + view.toBitmap() + } else { + wellCountView.visibility = View.GONE + BitmapDescriptorFactory.fromResource(R.mipmap.well_location).bitmap + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml new file mode 100644 index 0000000..e329b7d --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml new file mode 100644 index 0000000..15e6a4b --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml new file mode 100644 index 0000000..0c2abfe --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_short_line.xml b/app/src/main/res/drawable/bottom_short_line.xml new file mode 100644 index 0000000..10c9062 --- /dev/null +++ b/app/src/main/res/drawable/bottom_short_line.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml index 3cec51f..85a87c8 100644 --- a/app/src/main/res/layout/activity_add_device.xml +++ b/app/src/main/res/layout/activity_add_device.xml @@ -196,7 +196,7 @@ android:textSize="@dimen/sp_14" /> - - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt new file mode 100644 index 0000000..cb14054 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt @@ -0,0 +1,102 @@ +package com.casic.smarttube.widgets + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import com.amap.api.maps.Projection +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.MarkerOptions +import com.casic.smarttube.R +import com.pengxh.kt.lite.extensions.toBitmap +import java.util.* + +class GaoDeClusterMarkerView( + private val context: Context, firstMarkers: MarkerOptions, + projection: Projection, gridSize: Int +) { + //当前可观区域里的 聚合过之后的集合 + private val includeMarkers: ArrayList + + // 创建区域 + val bounds: LatLngBounds + var options: MarkerOptions = MarkerOptions() + + init { + val screenLocation = projection.toScreenLocation(firstMarkers.position) + //范围类 + val southwestPoint = Point(screenLocation.x - gridSize, screenLocation.y + gridSize) + //范围类 + val northeastPoint = Point(screenLocation.x + gridSize, screenLocation.y - gridSize) + bounds = LatLngBounds( + projection.fromScreenLocation(southwestPoint), + projection.fromScreenLocation(northeastPoint) + ) + //设置初始化marker属性 + options.anchor(0.5f, 1.3f) + .title(firstMarkers.title) + .position(firstMarkers.position) + .icon(firstMarkers.icon) + .snippet(firstMarkers.snippet) + .draggable(false) + includeMarkers = ArrayList() + includeMarkers.add(firstMarkers) + } + + /** + * 添加marker + */ + fun addMarker(markerOptions: MarkerOptions) { + includeMarkers.add(markerOptions) // 添加到列表中 + } + + /** + * 设置聚合点的中心位置以及图标 + */ + fun setPositionAndIcon() { + val size = includeMarkers.size + var lat = 0.0 + var lng = 0.0 + // 一个的时候 + if (size == 1) { //设置marker单个属性 + // 设置marker位置 + options.position( + LatLng( + includeMarkers[0].position.latitude, + includeMarkers[0].position.longitude + ) + ) + } else { // 聚合的时候 + //设置marker聚合属性 + for (op in includeMarkers) { + lat += op.position.latitude + lng += op.position.longitude + } + // 设置marker的位置为中心位置为聚集点的平均位置 + options.position(LatLng(lat / size, lng / size)) + } + options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap(size))) + } + + /** + * marker视图 + */ + private fun getBitmap(num: Int): Bitmap? { + val view = LayoutInflater.from(context).inflate(R.layout.marker_gaode, null) + val wellCountView = view.findViewById(R.id.wellCountView) + return if (num > 1) { + wellCountView.visibility = View.VISIBLE + wellCountView.text = String.format("${num}个") + wellCountView.gravity = Gravity.CENTER + view.toBitmap() + } else { + wellCountView.visibility = View.GONE + BitmapDescriptorFactory.fromResource(R.mipmap.well_location).bitmap + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml new file mode 100644 index 0000000..e329b7d --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml new file mode 100644 index 0000000..15e6a4b --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml new file mode 100644 index 0000000..0c2abfe --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_short_line.xml b/app/src/main/res/drawable/bottom_short_line.xml new file mode 100644 index 0000000..10c9062 --- /dev/null +++ b/app/src/main/res/drawable/bottom_short_line.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml index 3cec51f..85a87c8 100644 --- a/app/src/main/res/layout/activity_add_device.xml +++ b/app/src/main/res/layout/activity_add_device.xml @@ -196,7 +196,7 @@ android:textSize="@dimen/sp_14" /> - - + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt new file mode 100644 index 0000000..cb14054 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt @@ -0,0 +1,102 @@ +package com.casic.smarttube.widgets + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import com.amap.api.maps.Projection +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.MarkerOptions +import com.casic.smarttube.R +import com.pengxh.kt.lite.extensions.toBitmap +import java.util.* + +class GaoDeClusterMarkerView( + private val context: Context, firstMarkers: MarkerOptions, + projection: Projection, gridSize: Int +) { + //当前可观区域里的 聚合过之后的集合 + private val includeMarkers: ArrayList + + // 创建区域 + val bounds: LatLngBounds + var options: MarkerOptions = MarkerOptions() + + init { + val screenLocation = projection.toScreenLocation(firstMarkers.position) + //范围类 + val southwestPoint = Point(screenLocation.x - gridSize, screenLocation.y + gridSize) + //范围类 + val northeastPoint = Point(screenLocation.x + gridSize, screenLocation.y - gridSize) + bounds = LatLngBounds( + projection.fromScreenLocation(southwestPoint), + projection.fromScreenLocation(northeastPoint) + ) + //设置初始化marker属性 + options.anchor(0.5f, 1.3f) + .title(firstMarkers.title) + .position(firstMarkers.position) + .icon(firstMarkers.icon) + .snippet(firstMarkers.snippet) + .draggable(false) + includeMarkers = ArrayList() + includeMarkers.add(firstMarkers) + } + + /** + * 添加marker + */ + fun addMarker(markerOptions: MarkerOptions) { + includeMarkers.add(markerOptions) // 添加到列表中 + } + + /** + * 设置聚合点的中心位置以及图标 + */ + fun setPositionAndIcon() { + val size = includeMarkers.size + var lat = 0.0 + var lng = 0.0 + // 一个的时候 + if (size == 1) { //设置marker单个属性 + // 设置marker位置 + options.position( + LatLng( + includeMarkers[0].position.latitude, + includeMarkers[0].position.longitude + ) + ) + } else { // 聚合的时候 + //设置marker聚合属性 + for (op in includeMarkers) { + lat += op.position.latitude + lng += op.position.longitude + } + // 设置marker的位置为中心位置为聚集点的平均位置 + options.position(LatLng(lat / size, lng / size)) + } + options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap(size))) + } + + /** + * marker视图 + */ + private fun getBitmap(num: Int): Bitmap? { + val view = LayoutInflater.from(context).inflate(R.layout.marker_gaode, null) + val wellCountView = view.findViewById(R.id.wellCountView) + return if (num > 1) { + wellCountView.visibility = View.VISIBLE + wellCountView.text = String.format("${num}个") + wellCountView.gravity = Gravity.CENTER + view.toBitmap() + } else { + wellCountView.visibility = View.GONE + BitmapDescriptorFactory.fromResource(R.mipmap.well_location).bitmap + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml new file mode 100644 index 0000000..e329b7d --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml new file mode 100644 index 0000000..15e6a4b --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml new file mode 100644 index 0000000..0c2abfe --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_short_line.xml b/app/src/main/res/drawable/bottom_short_line.xml new file mode 100644 index 0000000..10c9062 --- /dev/null +++ b/app/src/main/res/drawable/bottom_short_line.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml index 3cec51f..85a87c8 100644 --- a/app/src/main/res/layout/activity_add_device.xml +++ b/app/src/main/res/layout/activity_add_device.xml @@ -196,7 +196,7 @@ android:textSize="@dimen/sp_14" /> - - + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + android:textColor="@color/hintTextColor" /> \ No newline at end of file diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt new file mode 100644 index 0000000..cb14054 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt @@ -0,0 +1,102 @@ +package com.casic.smarttube.widgets + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import com.amap.api.maps.Projection +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.MarkerOptions +import com.casic.smarttube.R +import com.pengxh.kt.lite.extensions.toBitmap +import java.util.* + +class GaoDeClusterMarkerView( + private val context: Context, firstMarkers: MarkerOptions, + projection: Projection, gridSize: Int +) { + //当前可观区域里的 聚合过之后的集合 + private val includeMarkers: ArrayList + + // 创建区域 + val bounds: LatLngBounds + var options: MarkerOptions = MarkerOptions() + + init { + val screenLocation = projection.toScreenLocation(firstMarkers.position) + //范围类 + val southwestPoint = Point(screenLocation.x - gridSize, screenLocation.y + gridSize) + //范围类 + val northeastPoint = Point(screenLocation.x + gridSize, screenLocation.y - gridSize) + bounds = LatLngBounds( + projection.fromScreenLocation(southwestPoint), + projection.fromScreenLocation(northeastPoint) + ) + //设置初始化marker属性 + options.anchor(0.5f, 1.3f) + .title(firstMarkers.title) + .position(firstMarkers.position) + .icon(firstMarkers.icon) + .snippet(firstMarkers.snippet) + .draggable(false) + includeMarkers = ArrayList() + includeMarkers.add(firstMarkers) + } + + /** + * 添加marker + */ + fun addMarker(markerOptions: MarkerOptions) { + includeMarkers.add(markerOptions) // 添加到列表中 + } + + /** + * 设置聚合点的中心位置以及图标 + */ + fun setPositionAndIcon() { + val size = includeMarkers.size + var lat = 0.0 + var lng = 0.0 + // 一个的时候 + if (size == 1) { //设置marker单个属性 + // 设置marker位置 + options.position( + LatLng( + includeMarkers[0].position.latitude, + includeMarkers[0].position.longitude + ) + ) + } else { // 聚合的时候 + //设置marker聚合属性 + for (op in includeMarkers) { + lat += op.position.latitude + lng += op.position.longitude + } + // 设置marker的位置为中心位置为聚集点的平均位置 + options.position(LatLng(lat / size, lng / size)) + } + options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap(size))) + } + + /** + * marker视图 + */ + private fun getBitmap(num: Int): Bitmap? { + val view = LayoutInflater.from(context).inflate(R.layout.marker_gaode, null) + val wellCountView = view.findViewById(R.id.wellCountView) + return if (num > 1) { + wellCountView.visibility = View.VISIBLE + wellCountView.text = String.format("${num}个") + wellCountView.gravity = Gravity.CENTER + view.toBitmap() + } else { + wellCountView.visibility = View.GONE + BitmapDescriptorFactory.fromResource(R.mipmap.well_location).bitmap + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml new file mode 100644 index 0000000..e329b7d --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml new file mode 100644 index 0000000..15e6a4b --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml new file mode 100644 index 0000000..0c2abfe --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_short_line.xml b/app/src/main/res/drawable/bottom_short_line.xml new file mode 100644 index 0000000..10c9062 --- /dev/null +++ b/app/src/main/res/drawable/bottom_short_line.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml index 3cec51f..85a87c8 100644 --- a/app/src/main/res/layout/activity_add_device.xml +++ b/app/src/main/res/layout/activity_add_device.xml @@ -196,7 +196,7 @@ android:textSize="@dimen/sp_14" /> - - + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + android:textColor="@color/hintTextColor" /> \ No newline at end of file diff --git a/app/src/main/res/layout/marker_gaode.xml b/app/src/main/res/layout/marker_gaode.xml new file mode 100644 index 0000000..15e9368 --- /dev/null +++ b/app/src/main/res/layout/marker_gaode.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt new file mode 100644 index 0000000..cb14054 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt @@ -0,0 +1,102 @@ +package com.casic.smarttube.widgets + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import com.amap.api.maps.Projection +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.MarkerOptions +import com.casic.smarttube.R +import com.pengxh.kt.lite.extensions.toBitmap +import java.util.* + +class GaoDeClusterMarkerView( + private val context: Context, firstMarkers: MarkerOptions, + projection: Projection, gridSize: Int +) { + //当前可观区域里的 聚合过之后的集合 + private val includeMarkers: ArrayList + + // 创建区域 + val bounds: LatLngBounds + var options: MarkerOptions = MarkerOptions() + + init { + val screenLocation = projection.toScreenLocation(firstMarkers.position) + //范围类 + val southwestPoint = Point(screenLocation.x - gridSize, screenLocation.y + gridSize) + //范围类 + val northeastPoint = Point(screenLocation.x + gridSize, screenLocation.y - gridSize) + bounds = LatLngBounds( + projection.fromScreenLocation(southwestPoint), + projection.fromScreenLocation(northeastPoint) + ) + //设置初始化marker属性 + options.anchor(0.5f, 1.3f) + .title(firstMarkers.title) + .position(firstMarkers.position) + .icon(firstMarkers.icon) + .snippet(firstMarkers.snippet) + .draggable(false) + includeMarkers = ArrayList() + includeMarkers.add(firstMarkers) + } + + /** + * 添加marker + */ + fun addMarker(markerOptions: MarkerOptions) { + includeMarkers.add(markerOptions) // 添加到列表中 + } + + /** + * 设置聚合点的中心位置以及图标 + */ + fun setPositionAndIcon() { + val size = includeMarkers.size + var lat = 0.0 + var lng = 0.0 + // 一个的时候 + if (size == 1) { //设置marker单个属性 + // 设置marker位置 + options.position( + LatLng( + includeMarkers[0].position.latitude, + includeMarkers[0].position.longitude + ) + ) + } else { // 聚合的时候 + //设置marker聚合属性 + for (op in includeMarkers) { + lat += op.position.latitude + lng += op.position.longitude + } + // 设置marker的位置为中心位置为聚集点的平均位置 + options.position(LatLng(lat / size, lng / size)) + } + options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap(size))) + } + + /** + * marker视图 + */ + private fun getBitmap(num: Int): Bitmap? { + val view = LayoutInflater.from(context).inflate(R.layout.marker_gaode, null) + val wellCountView = view.findViewById(R.id.wellCountView) + return if (num > 1) { + wellCountView.visibility = View.VISIBLE + wellCountView.text = String.format("${num}个") + wellCountView.gravity = Gravity.CENTER + view.toBitmap() + } else { + wellCountView.visibility = View.GONE + BitmapDescriptorFactory.fromResource(R.mipmap.well_location).bitmap + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml new file mode 100644 index 0000000..e329b7d --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml new file mode 100644 index 0000000..15e6a4b --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml new file mode 100644 index 0000000..0c2abfe --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_short_line.xml b/app/src/main/res/drawable/bottom_short_line.xml new file mode 100644 index 0000000..10c9062 --- /dev/null +++ b/app/src/main/res/drawable/bottom_short_line.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml index 3cec51f..85a87c8 100644 --- a/app/src/main/res/layout/activity_add_device.xml +++ b/app/src/main/res/layout/activity_add_device.xml @@ -196,7 +196,7 @@ android:textSize="@dimen/sp_14" /> - - + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + android:textColor="@color/hintTextColor" /> \ No newline at end of file diff --git a/app/src/main/res/layout/marker_gaode.xml b/app/src/main/res/layout/marker_gaode.xml new file mode 100644 index 0000000..15e9368 --- /dev/null +++ b/app/src/main/res/layout/marker_gaode.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/popu_map_info.xml b/app/src/main/res/layout/popu_map_info.xml new file mode 100644 index 0000000..bd6b147 --- /dev/null +++ b/app/src/main/res/layout/popu_map_info.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/libs/lite-release.aar b/app/libs/lite-release.aar index d9d51ae..f4b81e1 100644 --- a/app/libs/lite-release.aar +++ b/app/libs/lite-release.aar Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58bd05b..05d1d6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/QMUI.Compat.NoActionBar" + android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> - + + + = ArrayList() + + /** + * 视野内的marker + */ + private var markerOptionsInView: MutableList = ArrayList() + + /** + * 自定义Marker弹出框 + * */ + private var infoWindow: View? = null + + /** + * 所有设备列表信息集合 + * */ + private var deviceModels: MutableList = ArrayList() override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { homeView = inflater.inflate(R.layout.fragment_home, container, false) @@ -43,6 +78,62 @@ easyPopupWindow.showAsDropDown(homeView.rightOptionView, x, 0) } + //代码设置底部拉升距离 + val bottomSheetBehavior = BottomSheetBehavior.from(homeView.bottomBehaviorLayout) + homeView.coordinatorLayout.post { + bottomSheetBehavior.isFitToContents = false + bottomSheetBehavior.peekHeight = 30f.dp2px(requireContext()) + bottomSheetBehavior.isHideable = false + } + + //初始化vm + deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java) + + //获取所有窨井数据 + httpCountDownTimer.start() + DialogHelper.showLoadingDialog(requireActivity(), "数据加载中,请稍后...") + //获取所有设备数据 + deviceViewModel.obtainMapDeviceList() + deviceViewModel.mapDeviceModel.observe(this, { + if (it.code == 200) { + val latitudeList: MutableList = ArrayList() + val longitudeList: MutableList = ArrayList() + it.data.forEach { device -> + val lat = device.latGaode.toString() + val lng = device.lngGaode.toString() + if (lat.isNotBlank() && lng.isNotBlank()) { + //返回true代表当前位置在大陆、港澳地区,反之不在 + val latitude = lat.toDouble() + val longitude = lng.toDouble() + if (CoordinateConverter.isAMapDataAvailable(latitude, longitude)) { + //缓存所有设备详情,点击Marker时候需要 + deviceModels.add(device) + //分别缓存经、纬度 + latitudeList.add(latitude) + longitudeList.add(longitude) + //将所有设备信息转化缓存为Marker点 + allMarkerOptions.add( + MarkerOptions() + .position(LatLng(latitude, longitude)) + .title(device.wellName) + .snippet(device.wellCode) + ) + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度不在国内,异常经纬度 ===> [${lng},${lat}]") + } + } else { + Log.d(kTag, "${device.wellCode}闸井经纬度异常,异常经纬度 ===> [${lng},${lat}]") + } + } + //计算所有点的中心点位置 + val centerLatLng = LatLng(latitudeList.average(), longitudeList.average()) + //移动到指定经纬度 + val cameraPosition = CameraPosition(centerLatLng, 12f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + } + }) + //地图初始化 initMap(savedInstanceState) @@ -64,13 +155,173 @@ uiSettings.isRotateGesturesEnabled = false//不允许地图旋转 // 地图加载成功监听 -// aMap.addOnMapLoadedListener(this) + aMap.addOnMapLoadedListener(this) // 地图缩放监听 -// aMap.addOnCameraChangeListener(this) + aMap.addOnCameraChangeListener(this) // marker 点击事件监听 -// aMap.addOnMarkerClickListener(this) + aMap.addOnMarkerClickListener(this) // 点击marker弹出自定义popup -// aMap.setInfoWindowAdapter(this) + aMap.setInfoWindowAdapter(this) + } + + override fun onMapLoaded() { + //地图加载成功之后显示聚合点数据 + initClustersMarkers() + } + + override fun onCameraChange(p0: CameraPosition?) { + + } + + /** + * http请求计时器 + * */ + private val httpCountDownTimer = object : CountDownTimer(15 * 1000, 1000) { + override fun onFinish() { + "请求服务器超时,请退出后重试".show(requireContext()) + DialogHelper.dismissLoadingDialog() + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + //获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker + override fun onCameraChangeFinish(p0: CameraPosition?) { + //地图缩放之后显示聚合点数据 + DialogHelper.dismissLoadingDialog() + httpCountDownTimer.cancel() + initClustersMarkers() + } + + private fun initClustersMarkers() { + val proj = aMap.projection + val dm = resources.displayMetrics + var screenLocation: Point + markerOptionsInView.clear() + // 获取在当前视野内的marker + allMarkerOptions.forEach { + screenLocation = proj.toScreenLocation(it.position) + if (screenLocation.x >= 0 && screenLocation.y >= 0 && screenLocation.x <= dm.widthPixels && screenLocation.y <= dm.heightPixels) { + //在当前可观区域内 + markerOptionsInView.add(it) + } + } + // 自定义的聚合类MarkerCluster + val clustersMarkers: MutableList = ArrayList() + markerOptionsInView.forEach { + if (clustersMarkers.size == 0) { + //添加一个新的自定义marker + clustersMarkers.add( + GaoDeClusterMarkerView(requireContext(), it, proj, LocaleConstant.RADIUS_SIZE) + ) + } else { + var isInRange = false + //Kotlin foreach不能用break + for (view in clustersMarkers) { + //判断当前的marker是否在前面marker的聚合范围内 并且每个marker只会聚合一次。 + if (view.bounds.contains(it.position)) { + view.addMarker(it) + isInRange = true + break + } + } + //如果没在任何范围内,自己单独形成一个自定义marker。在和后面的marker进行比较 + if (!isInRange) { + clustersMarkers.add( + GaoDeClusterMarkerView( + requireContext(), + it, + proj, + LocaleConstant.RADIUS_SIZE + ) + )//相距多少才聚合 + } + } + } + // 设置聚合点的位置和icon + clustersMarkers.forEach { + it.setPositionAndIcon() + } + aMap.clear() + // 重新添加 marker + clustersMarkers.forEach { + aMap.addMarker(it.options) + } + } + + override fun onMarkerClick(marker: Marker?): Boolean { + //显示闸井信息 + marker?.showInfoWindow() + return true + } + + override fun getInfoWindow(marker: Marker?): View? { + if (infoWindow == null) { + infoWindow = LayoutInflater.from(requireContext()).inflate(R.layout.popu_map_info, null) + } + val v = infoWindow!! + //反射得到popup里面的控件对象 + val wellNameView = v.findViewById(R.id.wellNameView) + val wellCodeView = v.findViewById(R.id.wellCodeView) + val wellTypeView = v.findViewById(R.id.wellTypeView) + val wellStateView = v.findViewById(R.id.wellStateView) + val deepView = v.findViewById(R.id.deepView) + val locationView = v.findViewById(R.id.locationView) + + //绑定数据 + val clickedLatLng = marker?.position!! + for (device in deviceModels) { + if (clickedLatLng.latitude == device.latGaode!!.toDouble() + && clickedLatLng.longitude == device.lngGaode!!.toDouble() + ) { + wellCodeView.text = String.format("闸井编号: ${device.wellCode}") + wellNameView.text = String.format("闸井名称: ${device.wellName}") + wellTypeView.text = String.format("闸井类型: ${device.wellType}") + wellStateView.text = String.format("布/撤防状态: ${device.bfzt}") + deepView.text = String.format("闸井深度: ${device.deep}m") + locationView.text = String.format("详细位置: ${device.position}") + return infoWindow + } + } + "无法查看聚合点,请放大比例后再查看".show(requireContext()) + return null + } + + /** + * 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框 + * */ + override fun getInfoContents(p0: Marker?): View? = null + + override fun onInfoWindowClick(p0: Marker?) { + if (p0 != null) { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("操作提示") + .setMessage("确定要前往吗") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + val latLng = p0.position + val lat = latLng.latitude.toString() + val lng = latLng.longitude.toString() + if (lat.isBlank() || lng.isBlank()) { + "窨井经纬度异常,无法开启导航".show(requireContext()) + return + } + Poi(p0.snippet, LatLng(lat.toDouble(), lng.toDouble()), "").showRouteOnMap( + requireContext() + ) + } + + override fun onCancelClick() { + + } + }).build().show() + } } /***以下是地图生命周期管理************************************************************************/ diff --git a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt index 1618f24..73d1e4c 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/MinePageFragment.kt @@ -139,6 +139,7 @@ DialogHelper.dismissLoadingDialog() "同步完成".show(requireContext()) userData = it.data + updateUserInfo() } }) @@ -218,14 +219,14 @@ private fun updateUserInfo() { //设置头像,圆形,暂时是默认的 - val roundDrawable = - BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) - .createRoundDrawable( - requireContext(), - 3f.dp2px(requireContext()), - R.color.mainThemeColor.convertColor(requireContext()) - ) - userImageView.setImageDrawable(roundDrawable) +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) userNameView.text = userData.name userPhoneView.text = String.format("电话:${userData.phone}") userDeptView.text = String.format("部门:${userData.deptName}") diff --git a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt index 5b18ed5..d80e8ed 100644 --- a/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt +++ b/app/src/main/java/com/casic/smarttube/fragment/OverviewFragment.kt @@ -61,31 +61,31 @@ obtainDeviceList() } - deviceViewModel.deviceListModel.observe(this, { - if (it.code == 200) { - val dataRows = it.data?.rows - when { - isRefresh -> { - dataBeans.clear() - dataBeans = dataRows!! - overviewLayout.finishRefresh() - isRefresh = false - } - isLoadMore -> { - if (dataRows?.size == 0) { - "到底了,别拉了".show(requireContext()) - } - dataBeans.addAll(dataRows!!) - overviewLayout.finishLoadMore() - isLoadMore = false - } - else -> { - dataBeans = dataRows!! - } - } - weakReferenceHandler.sendEmptyMessage(2022062401) - } - }) +// deviceViewModel.deviceListModel.observe(this, { +// if (it.code == 200) { +// val dataRows = it.data?.rows +// when { +// isRefresh -> { +// dataBeans.clear() +// dataBeans = dataRows!! +// overviewLayout.finishRefresh() +// isRefresh = false +// } +// isLoadMore -> { +// if (dataRows?.size == 0) { +// "到底了,别拉了".show(requireContext()) +// } +// dataBeans.addAll(dataRows!!) +// overviewLayout.finishLoadMore() +// isLoadMore = false +// } +// else -> { +// dataBeans = dataRows!! +// } +// } +// weakReferenceHandler.sendEmptyMessage(2022062401) +// } +// }) } private fun obtainDeviceList() { diff --git a/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java new file mode 100644 index 0000000..7e8a367 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/model/MapDeviceModel.java @@ -0,0 +1,190 @@ +package com.casic.smarttube.model; + +import java.util.List; + +public class MapDeviceModel { + + private int code; + private List data; + private String message; + private String success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getSuccess() { + return success; + } + + public void setSuccess(String success) { + this.success = success; + } + + public static class DataBean { + private String area; + private int deep; + private String responsibleDept; + private String wellCode; + private String groupId; + private String deptid; + private String staff; + private String photos; + private String wellType; + private String valid; + private String bfzt; + private String lngGaode; + private String latGaode; + private String wellName; + private String tel; + private String position; + + public String getArea() { + return area; + } + + public void setArea(String area) { + this.area = area; + } + + public int getDeep() { + return deep; + } + + public void setDeep(int deep) { + this.deep = deep; + } + + public String getResponsibleDept() { + return responsibleDept; + } + + public void setResponsibleDept(String responsibleDept) { + this.responsibleDept = responsibleDept; + } + + public String getWellCode() { + return wellCode; + } + + public void setWellCode(String wellCode) { + this.wellCode = wellCode; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getDeptid() { + return deptid; + } + + public void setDeptid(String deptid) { + this.deptid = deptid; + } + + public String getStaff() { + return staff; + } + + public void setStaff(String staff) { + this.staff = staff; + } + + public String getPhotos() { + return photos; + } + + public void setPhotos(String photos) { + this.photos = photos; + } + + public String getWellType() { + return wellType; + } + + public void setWellType(String wellType) { + this.wellType = wellType; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getBfzt() { + return bfzt; + } + + public void setBfzt(String bfzt) { + this.bfzt = bfzt; + } + + public String getLngGaode() { + return lngGaode; + } + + public void setLngGaode(String lngGaode) { + this.lngGaode = lngGaode; + } + + public String getLatGaode() { + return latGaode; + } + + public void setLatGaode(String latGaode) { + this.latGaode = latGaode; + } + + public String getWellName() { + return wellName; + } + + public void setWellName(String wellName) { + this.wellName = wellName; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } +} diff --git a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt index af85fc7..4908afb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smarttube/utils/LocaleConstant.kt @@ -26,6 +26,7 @@ * */ const val PERMISSIONS_CODE = 999 const val PAGE_LIMIT = 20 + const val RADIUS_SIZE = 50 //相距多少米才聚合,单位:米 /** * ============================================================================================= @@ -40,6 +41,7 @@ * ============================================================================================= * */ const val USER_DETAIL_MODEL = "userDetailModel" +// const val SERVER_BASE_URL = "http://192.168.43.19:11643" const val SERVER_BASE_URL = "http://111.198.10.15:11304" const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" const val ACCOUNT = "account" diff --git a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt index 14e2f56..a2c1338 100644 --- a/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt +++ b/app/src/main/java/com/casic/smarttube/utils/RSAUtils.kt @@ -34,17 +34,14 @@ } fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { - val resultBytes = - processData(srcData, publicKey) + val resultBytes = processData(srcData, publicKey) return Base64.encodeToString(resultBytes, Base64.DEFAULT) } fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { var publicKey: PublicKey? = null - val keyBytes = - Base64.decode(publicKeyStr, Base64.DEFAULT) - val keySpec = - X509EncodedKeySpec(keyBytes) + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) try { val keyFactory = KeyFactory.getInstance("RSA") publicKey = keyFactory.generatePublic(keySpec) diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt index 738cc78..83cacb9 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitService.kt @@ -19,7 +19,7 @@ * @param secretKey 加密后的密码 */ @FormUrlEncoded - @POST("/user/login") + @POST("/user/appLogin") suspend fun obtainLoginResult( @Field("sid") sid: String, @Field("username") account: String, @@ -71,6 +71,12 @@ suspend fun obtainVersionResult(@Header("token") token: String): String /** + * 地图设备列表 + */ + @POST("/tube/well/list") + suspend fun obtainMapDeviceList(@Header("token") token: String): String + + /** * 获取管盯分页列表 * */ @GET("/device/list") diff --git a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt index c51557a..1e04ceb 100644 --- a/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smarttube/utils/retrofit/RetrofitServiceManager.kt @@ -62,6 +62,13 @@ } /** + * 地图设备列表 + */ + suspend fun obtainMapDeviceList(): String { + return api.obtainMapDeviceList(AuthenticationHelper.token!!) + } + + /** * 根据设备类型获取设备分页列表 */ suspend fun obtainDeviceListByType( diff --git a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt index 910c4a9..f10db67 100644 --- a/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt +++ b/app/src/main/java/com/casic/smarttube/view/AddDeviceActivity.kt @@ -23,10 +23,8 @@ import com.luck.picture.lib.config.SelectMimeType import com.luck.picture.lib.entity.LocalMedia import com.luck.picture.lib.interfaces.OnResultCallbackListener -import com.pengxh.kt.lite.activity.BigImageActivity import com.pengxh.kt.lite.adapter.EditableImageAdapter import com.pengxh.kt.lite.base.KotlinBaseActivity -import com.pengxh.kt.lite.callback.OnImageCompressListener import com.pengxh.kt.lite.extensions.* import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -161,7 +159,7 @@ if (realPaths[position].isEmpty()) { "图片加载失败,无法查看大图".show(context) } else { - navigatePageTo(position, realPaths) +// navigatePageTo(position, realPaths) } } @@ -298,17 +296,17 @@ // Log.d(kTag, "绝对路径:" + result.realPath) // Log.d(kTag, "原图路径:" + result.originalPath) // Log.d(kTag, "沙盒路径:" + result.sandboxPath) - result.realPath.compressImage(this, object : OnImageCompressListener { - override fun onSuccess(file: File) { - Log.d(kTag, "onSuccess: " + file.absolutePath) - //上传图片 - uploadImageViewModel.uploadImage(file) - } - - override fun onError(e: Throwable) { - e.printStackTrace() - } - }) +// result.realPath.compressImage(this, object : OnImageCompressListener { +// override fun onSuccess(file: File) { +// Log.d(kTag, "onSuccess: " + file.absolutePath) +// //上传图片 +// uploadImageViewModel.uploadImage(file) +// } +// +// override fun onError(e: Throwable) { +// e.printStackTrace() +// } +// }) } private lateinit var loadingDialog: QMUITipDialog diff --git a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt index 4d0b36f..5b2f97f 100644 --- a/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt +++ b/app/src/main/java/com/casic/smarttube/vm/DeviceViewModel.kt @@ -6,6 +6,7 @@ import com.casic.smarttube.extensions.toErrorMessage import com.casic.smarttube.model.DeviceHistoryDataModel import com.casic.smarttube.model.DeviceListModel +import com.casic.smarttube.model.MapDeviceModel import com.casic.smarttube.utils.retrofit.RetrofitServiceManager import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -16,9 +17,23 @@ class DeviceViewModel : BaseViewModel() { private val gson = Gson() - val deviceListModel = MutableLiveData() + val mapDeviceModel = MutableLiveData() val historyDataModel = MutableLiveData() + fun obtainMapDeviceList() = launch({ + val response = RetrofitServiceManager.obtainMapDeviceList() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + mapDeviceModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) + fun obtainDeviceListByType( deptid: String, keywords: String, isOnline: String, page: Int ) = launch({ @@ -26,9 +41,9 @@ RetrofitServiceManager.obtainDeviceListByType(deptid, keywords, isOnline, page) val responseCode = response.separateResponseCode() if (responseCode == 200) { - deviceListModel.value = gson.fromJson( - response, object : TypeToken() {}.type - ) +// deviceListModel.value = gson.fromJson( +// response, object : TypeToken() {}.type +// ) } else { response.toErrorMessage().show(BaseApplication.obtainInstance()) } diff --git a/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt new file mode 100644 index 0000000..cb14054 --- /dev/null +++ b/app/src/main/java/com/casic/smarttube/widgets/GaoDeClusterMarkerView.kt @@ -0,0 +1,102 @@ +package com.casic.smarttube.widgets + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import com.amap.api.maps.Projection +import com.amap.api.maps.model.BitmapDescriptorFactory +import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.LatLngBounds +import com.amap.api.maps.model.MarkerOptions +import com.casic.smarttube.R +import com.pengxh.kt.lite.extensions.toBitmap +import java.util.* + +class GaoDeClusterMarkerView( + private val context: Context, firstMarkers: MarkerOptions, + projection: Projection, gridSize: Int +) { + //当前可观区域里的 聚合过之后的集合 + private val includeMarkers: ArrayList + + // 创建区域 + val bounds: LatLngBounds + var options: MarkerOptions = MarkerOptions() + + init { + val screenLocation = projection.toScreenLocation(firstMarkers.position) + //范围类 + val southwestPoint = Point(screenLocation.x - gridSize, screenLocation.y + gridSize) + //范围类 + val northeastPoint = Point(screenLocation.x + gridSize, screenLocation.y - gridSize) + bounds = LatLngBounds( + projection.fromScreenLocation(southwestPoint), + projection.fromScreenLocation(northeastPoint) + ) + //设置初始化marker属性 + options.anchor(0.5f, 1.3f) + .title(firstMarkers.title) + .position(firstMarkers.position) + .icon(firstMarkers.icon) + .snippet(firstMarkers.snippet) + .draggable(false) + includeMarkers = ArrayList() + includeMarkers.add(firstMarkers) + } + + /** + * 添加marker + */ + fun addMarker(markerOptions: MarkerOptions) { + includeMarkers.add(markerOptions) // 添加到列表中 + } + + /** + * 设置聚合点的中心位置以及图标 + */ + fun setPositionAndIcon() { + val size = includeMarkers.size + var lat = 0.0 + var lng = 0.0 + // 一个的时候 + if (size == 1) { //设置marker单个属性 + // 设置marker位置 + options.position( + LatLng( + includeMarkers[0].position.latitude, + includeMarkers[0].position.longitude + ) + ) + } else { // 聚合的时候 + //设置marker聚合属性 + for (op in includeMarkers) { + lat += op.position.latitude + lng += op.position.longitude + } + // 设置marker的位置为中心位置为聚集点的平均位置 + options.position(LatLng(lat / size, lng / size)) + } + options.icon(BitmapDescriptorFactory.fromBitmap(getBitmap(size))) + } + + /** + * marker视图 + */ + private fun getBitmap(num: Int): Bitmap? { + val view = LayoutInflater.from(context).inflate(R.layout.marker_gaode, null) + val wellCountView = view.findViewById(R.id.wellCountView) + return if (num > 1) { + wellCountView.visibility = View.VISIBLE + wellCountView.text = String.format("${num}个") + wellCountView.gravity = Gravity.CENTER + view.toBitmap() + } else { + wellCountView.visibility = View.GONE + BitmapDescriptorFactory.fromResource(R.mipmap.well_location).bitmap + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml new file mode 100644 index 0000000..e329b7d --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_main_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml new file mode 100644 index 0000000..15e6a4b --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_transparent_radius_10.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml new file mode 100644 index 0000000..0c2abfe --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_white_radius_top_20.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bottom_short_line.xml b/app/src/main/res/drawable/bottom_short_line.xml new file mode 100644 index 0000000..10c9062 --- /dev/null +++ b/app/src/main/res/drawable/bottom_short_line.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_device.xml b/app/src/main/res/layout/activity_add_device.xml index 3cec51f..85a87c8 100644 --- a/app/src/main/res/layout/activity_add_device.xml +++ b/app/src/main/res/layout/activity_add_device.xml @@ -196,7 +196,7 @@ android:textSize="@dimen/sp_14" /> - - + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + android:textColor="@color/hintTextColor" /> \ No newline at end of file diff --git a/app/src/main/res/layout/marker_gaode.xml b/app/src/main/res/layout/marker_gaode.xml new file mode 100644 index 0000000..15e9368 --- /dev/null +++ b/app/src/main/res/layout/marker_gaode.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/popu_map_info.xml b/app/src/main/res/layout/popu_map_info.xml new file mode 100644 index 0000000..bd6b147 --- /dev/null +++ b/app/src/main/res/layout/popu_map_info.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 5f23d7d..21317a9 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -3,9 +3,6 @@ @@ -100,4 +97,13 @@ @dimen/dp_5 true + +