编程 Android Studio降雨热力图 JJuprising 2023-06-08 2024-06-02
基础配置 AndroidManifest.xml 配置定位服务和百度地图key
1 2 3 4 5 6 7 8 9 <service android:name ="com.baidu.location.f" android:enabled ="true" android:process =":remote" > </service > <meta-data android:name ="com.baidu.lbsapi.API_KEY" android:value ="填写百度地图key" />
加权限
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <uses-permission android:name ="android.permission.INTERNET" /> <uses-permission android:name ="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name ="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name ="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name ="android.permission.ACCESS_COARSE_LOCATION" > </uses-permission > <uses-permission android:name ="android.permission.ACCESS_FINE_LOCATION" > </uses-permission > <uses-permission android:name ="android.permission.ACCESS_WIFI_STATE" > </uses-permission > <uses-permission android:name ="android.permission.CHANGE_WIFI_STATE" > </uses-permission > <uses-permission android:name ="android.permission.READ_PHONE_STATE" />
下载sdk 放到jinLibs下
build.gradle 添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 android { ... defaultConfig { ndk { abiFilters "armeabi" , "armeabi-v7a" , "arm64-v8a" , "x86" ,"x86_64" } } ... sourceSets { main { jniLibs.srcDir 'libs' } } } dependencies { ... implementation 'com.squareup.okhttp3:okhttp:4.8.1' implementation 'com.google.code.gson:gson:2.8.8' implementation files('libs\\BaiduLBS_Android.jar' ) }
生成地图 初始化 调整缩放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 private fun initloc () { mLocationClient = LocationClient(requireActivity()) val option = LocationClientOption() option.isOpenGps = true option.setCoorType("bd09ll" ) option.setScanSpan(1000 ) option.setAddrType("all" ) option.setIsNeedAddress(true ) option.setIsNeedLocationDescribe(true ) mLocationClient!!.locOption = option val myLocationListener = MyLocationListener() mLocationClient!!.registerLocationListener(myLocationListener) val builder = MapStatus.Builder() builder.zoom(8.0f ) mBaiduMap?.setMapStatus(MapStatusUpdateFactory.newMapStatus(builder.build())) mLocationClient!!.start() mBaiduMap?.animateMapStatus(preStatus) } inner class MyLocationListener : BDAbstractLocationListener () { override fun onReceiveLocation (location: BDLocation ) { if (location == null || mMapView == null ) { return } val locData = MyLocationData.Builder() .accuracy(location.radius) .direction(location.direction).latitude(location.latitude) .longitude(location.longitude).build() mBaiduMap?.setMyLocationData(locData) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 private fun initloc () { mLocationClient = LocationClient(requireActivity()) val option = LocationClientOption() option.isOpenGps = true option.setCoorType("bd09ll" ) option.setScanSpan(1000 ) option.setAddrType("all" ) option.setIsNeedAddress(true ) option.setIsNeedLocationDescribe(true ) mLocationClient!!.locOption = option val myLocationListener = MyLocationListener() mLocationClient!!.registerLocationListener(myLocationListener) val builder = MapStatus.Builder() builder.zoom(8.0f ) mBaiduMap?.setMapStatus(MapStatusUpdateFactory.newMapStatus(builder.build())) mLocationClient!!.start() mBaiduMap?.animateMapStatus(preStatus) } inner class MyLocationListener : BDAbstractLocationListener () { override fun onReceiveLocation (location: BDLocation ) { if (location == null || mMapView == null ) { return } val locData = MyLocationData.Builder() .accuracy(location.radius) .direction(location.direction).latitude(location.latitude) .longitude(location.longitude).build() mBaiduMap?.setMyLocationData(locData) } }
显示定位 监听器,获取当前位置,根据定位数据设置状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 inner class MyLocationListener : BDAbstractLocationListener () { override fun onReceiveLocation (location: BDLocation ) { ... preLatitude = String.format("%.2f" , location.latitude).toDouble() preLongitude = String.format("%.2f" , location.longitude).toDouble() preAddress = location.addrStr val ll = LatLng(location.latitude, location.longitude) preStatus = MapStatusUpdateFactory.newLatLng(ll) } }
动画方式回到定位位置
1 2 3 4 5 6 val setStatusBtn = binding.setStatusBtnsetStatusBtn.setOnClickListener { mBaiduMap?.animateMapStatus(preStatus) }
获取降雨数据 组建城市列表 预设几个城市,生成地图步骤获取当前位置的经纬度数据
1 2 3 4 5 6 7 8 9 10 11 12 private fun getRainData () { thread { val cities = listOf( Pair(preLatitude, preLongitude), Pair(39.90 , 116.41 ), Pair(31.23 , 121.47 ), ... ) } }
准备类和取数据方法 查看和风天气实例数据结构和属性,构建类
观察逐小时天气预报 返回数据,我们需要的是hourly里的pop数据,并希望把24小时的数据组成一个列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 { "code" : "200" , "updateTime" : "2021-02-16T13:35+08:00" , "fxLink" : "http://hfx.link/2ax1" , "hourly" : [ { "fxTime" : "2021-02-16T15:00+08:00" , "temp" : "2" , "icon" : "100" , "text" : "晴" , "wind360" : "335" , "windDir" : "西北风" , "windScale" : "3-4" , "windSpeed" : "20" , "humidity" : "11" , "pop" : "0" , "precip" : "0.0" , "pressure" : "1025" , "cloud" : "0" , "dew" : "-25" } , ...
因此我们需要先准备好类来接收数据,并准备读取pop列表的方法。并设置方法,提取需要的数据属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 data class HourlyData ( val fxTime: String, val temp: String, val icon: String, val text: String, val wind360: String, val windDir: String, val windScale: String, val windSpeed: String, val humidity: String, val pop: String, val precip: String, val pressure: String, val cloud: String, val dew: String ) class CityRainProbability (private val hourlyData: List<HourlyData>) { fun getHourlyPop () : List<Double > { return hourlyData.map { it.pop.toDouble() } } }
调用API组成降雨数据 遍历列表,解析读取JSON
在getRainData()
中补充,遍历城市列表,请求解析完数据后,构造成city
类,并存入城市降雨列表citiesRain
中
1 2 3 4 5 6 7 8 for (city in cities) { val location = city.second.toString() + "," + city.first.toString() println(location) val url = "https://devapi.qweather.com/v7/weather/24h?location=$location &key=$key " println(url) readJSONData(url, city) }
city类,包含了经纬度信息和24小时降雨概率列表
1 2 3 4 5 data class City ( val latLng: LatLng, val hourlyProbabilities: List<Double >? )
实例化类,获取降雨数据
主要分为两步,首先请求数据 ,然后解析数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private fun readJSONData (url: String , city: Pair <Double , Double >) { thread { try { val client = OkHttpClient() val request = Request.Builder().url(url).build() val response = client.newCall(request).execute() val responseData = response.body?.string() if (responseData != null ) { dealRainData(responseData, city) Log.d("responseData" , responseData) } } catch (e: Exception) { e.printStackTrace() } } } private fun dealRainData (JsData: String , city: Pair <Double , Double >) { val gson = Gson() val cityData = gson.fromJson(JsData, JsonObject::class .java) val hourlyData = gson.fromJson(cityData.getAsJsonArray("hourly" ), Array<HourlyData>::class .java).toList() val City = CityRainProbability(hourlyData) hourlyPop = City.getHourlyPop() val hourlyRainProb = hourlyPop?.toList() val thiscity = City(LatLng(city.first, city.second), hourlyRainProb) citiesRain.add(thiscity) }
组成降雨数据citiesRain,是一个City类的列表
1 private val citiesRain = mutableListOf<City>()
显示热力图和信息窗 热力图 帧数据 构建24帧数据
提取城市列表每个城市经纬度和当前小时的降雨概率组成代表当前小时的帧数据,降雨概率作为权重
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private fun showheat () { val builder = HeatMap.Builder() val frames = MutableList(24 ) { mutableListOf<WeightedLatLng>() } citiesRain.forEach { city -> val hourProbabilities = city.hourlyProbabilities hourProbabilities?.forEachIndexed { index, probability -> val weightedLatLng = WeightedLatLng(city.latLng, probability) frames[index].add(weightedLatLng) } } builder.weightedDatas(frames) }
配置热力图 设置帧变化动画属性,热力图半径,渐变颜色,透明度
热力图权值范围0-100
添加热力图覆盖物
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private fun showheat () { val init = HeatMapAnimation(true , 100 , HeatMapAnimation.AnimationType.Linear) val frame = HeatMapAnimation(true , 10000 , HeatMapAnimation.AnimationType.Linear) builder.initAnimation(init ) builder.frameAnimation(frame) builder.radius(35 ) val colors = intArrayOf( Color.rgb(255 , 0 , 0 ), Color.rgb(0 , 225 , 0 ), Color.rgb(0 , 0 , 200 ) ) builder.gradient(Gradient(colors, floatArrayOf(0.2f , 0.5f , 1.0f ))) builder.maxIntensity(100.0f ) builder.opacity(0.8 ) val heatMapData = builder.build() Log.d("showHeat" , "添加覆盖物" ) mBaiduMap?.addHeatMap(heatMapData) mBaiduMap?.startHeatMapFrameAnimation() }
进度条 进度条
1 2 3 4 5 6 7 <ProgressBar android:id ="@+id/determinateBar" style ="?android:attr/progressBarStyleHorizontal" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:max ="24" android:progress ="1" />
回调动态热力图帧索引,给进度条赋值,让进度条提示当前是第几帧(小时)的热力图
1 2 3 4 5 6 7 val progressText = binding.progressText mBaiduMap?.setOnHeatMapDrawFrameCallBack { indexCallBack -> progressBar.progress = indexCallBack progressText.text = "$indexCallBack 小时后" }
信息窗 遍历降雨数据找最大值,记录索引和最值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private fun countRainpro () { val preCity = citiesRain.firstOrNull() maxRainIndex = -1 maxRainValue = Double .MIN_VALUE preCity?.let { city -> val attributeList = city.hourlyProbabilities if (attributeList != null ) { for ((index, value) in attributeList.withIndex()) { if (value > maxRainValue) { maxRainValue = value maxRainIndex = index } } } } }
设置信息窗内容和位置
1 2 3 4 5 6 7 8 9 10 11 12 if (maxRainValue == 0.0 ) { messageBtn.text = "$preAddress \n24小时内不会下雨" } else { var rat = maxRainValue.toInt() messageBtn.text = "$preAddress \n $maxRainIndex 小时后有$rat %概率会下雨" } mInfoWindow = InfoWindow(messageBtn, LatLng(preLatitude, preLongitude), -100 )
监听器中使InfoWindow生效,这样子就能满足当移动地图时,信息窗能够保持在初始位置,不会乱飞。
1 2 3 4 5 6 7 8 9 inner class MyLocationListener : BDAbstractLocationListener () { override fun onReceiveLocation (location: BDLocation ) { ... if (mInfoWindow != null ) { mBaiduMap?.showInfoWindow(mInfoWindow) } } }
遇到的问题 无法在模拟器显示 进行真机调试,数据线连接安卓手机,手机开启usb调试,修改编辑器运行设备为手机
异步问题 协程方法/用按钮