feiyu02
2024-07-03 c9bbee8bb47d6f383f9699b59c046ddc0cb464e9
1. 新增走航报告自动道路识别模块
已修改4个文件
已添加1个文件
166 ■■■■■ 文件已修改
src/main/kotlin/com/flightfeather/uav/biz/dataprocess/TrackSegment.kt 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/common/location/CoordinateUtil.kt 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/service/RealTimeDataService.kt 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/RealTimeDataServiceImpl.kt 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/lightshare/web/RealTimeDataController.kt 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/kotlin/com/flightfeather/uav/biz/dataprocess/TrackSegment.kt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,113 @@
package com.flightfeather.uav.biz.dataprocess
import com.flightfeather.uav.common.location.CoordinateUtil
import com.flightfeather.uav.domain.entity.BaseRealTimeData
import java.math.BigDecimal
import kotlin.math.abs
/**
 * èµ°èˆªè½¨è¿¹åˆ†å‰²åˆ†ç±»
 * @date 2024/7/3
 * @author feiyu02
 */
object TrackSegment {
    // åæ ‡ç‚¹é—´æœ€å°è·ç¦»ï¼Œå•位米
    private const val MIN_DISTANCE = 10
    // ä¸¤æ¡ç›´çº¿å¤¹è§’为90度时,认为垂直。实际情况中,角度允许有一定偏差,允许偏差角度
    private const val VERTICAL_OFFSET_DEG = 22.5
    /**
     * æŒ‰ç…§é“路对走航轨迹进行分割
     * æ­¤å¤„简化认为当连续的前后两段轨迹趋向于一条直线(平行)时,即为在同一条道路上;当其更趋向于垂直时,即认为车辆进行了转向,进入了新的道路
     * å…·ä½“的计算方法为:
     * 1. æ±‚出每个坐标点相对于前一个坐标点在经纬度坐标系下偏转的角度(当前后两个坐标点的距离过近时,直接认为其同属于同一路段);
     * 2. ä¾æ¬¡æ¯”较前后两个偏转角度的差值,当差值角度在[45°, 135°]或[225°, 315°]范围内时,认为车辆进行了转向,进入新的道路,否则依旧处于同一道路上
     */
    fun segmentWithRoad(data: List<BaseRealTimeData>): List<List<BaseRealTimeData>> {
        val records = mutableListOf<MutableList<BaseRealTimeData>>()
        if (data.isEmpty()) return records
        // è®°å½•上一组接近平行的坐标点的偏转角度
        val lastDegList = mutableListOf<Double>()
        // è®°å½•上一组距离接近的坐标点
        val closeList = mutableListOf<BaseRealTimeData>()
        records.add(mutableListOf())
        data.forEachIndexed { i, d ->
            if (records.size == 33) {
                println(records.size)
            }
            var isSame = false
            if (i > 0) {
                // å‰ä¸€ä¸ªæœ‰æ•ˆç›‘测点
                val lastData = data[i - 1]
                // ç¡®ä¿ä¸¤ç‚¹åæ ‡åˆæ³•
                if ((lastData.longitude != null && lastData.longitude != BigDecimal.ZERO)
                    && (lastData.latitude != null && lastData.latitude != BigDecimal.ZERO)
                    && (d.longitude != null && d.longitude != BigDecimal.ZERO)
                    && (d.latitude != null && d.latitude != BigDecimal.ZERO)
                ) {
                    // è®¡ç®—两点距离,过近时直接认为是同一个点,同一路段
                    var distance = CoordinateUtil.calculateDistance(
                        lastData.longitude!!.toDouble(), lastData.latitude!!.toDouble(),
                        d.longitude!!.toDouble(), d.latitude!!.toDouble(),
                    )
                    // è·ç¦»è¿‡è¿‘æ—¶, å°†è·ç¦»æ›¿æ¢ä¸ºå½“前点和近点集合中的第一个点的距离
                    if (distance < MIN_DISTANCE && closeList.isNotEmpty()) {
                        // å¦‚果已经有距离过近的点集合,则还需要和第一个点进行距离判断,
                        // è§£å†³å½“车辆行驶速度过低时,连续点的距离都过近导致都判定为同一点的问题
                        val firstCloseData = closeList[0]
                        distance = CoordinateUtil.calculateDistance(
                            firstCloseData.longitude!!.toDouble(), firstCloseData.latitude!!.toDouble(),
                            d.longitude!!.toDouble(), d.latitude!!.toDouble())
                    }
                    if (distance >= MIN_DISTANCE) {
                        val deg = CoordinateUtil.getAngle(
                            lastData.longitude!!.toDouble(), lastData.latitude!!.toDouble(),
                            d.longitude!!.toDouble(), d.latitude!!.toDouble(),
                        )
                        isSame = if (lastDegList.isNotEmpty()) {
                            var bool = true
                            // å‡ºçŽ°è§’åº¦æŽ¥è¿‘åž‚ç›´çŠ¶æ€çš„æ¬¡æ•°
                            var unSameCount = 0
                            // æ¯”较当前方位角和上一组每个方位角的差值是否都处于范围内
                            for (lastDeg in lastDegList) {
                                val diffDeg = abs(deg - lastDeg)
                                if (diffDeg in (90.0 - VERTICAL_OFFSET_DEG)..(90.0 + VERTICAL_OFFSET_DEG)
                                    || diffDeg in (270.0 - VERTICAL_OFFSET_DEG)..(270.0 + VERTICAL_OFFSET_DEG)
                                ) {
                                    unSameCount++
                                }
                            }
                            // å½“接近垂直的角度超过上一组平行角度的一半时,认为从该点轨迹转弯(消除个别坐标点由于定位误差导致的错误影响)
                            bool = unSameCount < (lastDegList.size / 3 + 1)
                            // å½“出现转弯点时,清空历史角度,并且舍弃转弯点相对于前一个点的角度(解决一种极端情况,当连续出现转弯点时,当前坐标点会被单独分割为一段)
                            if (!bool) lastDegList.clear()
                            bool
                        } else {
                            // å½“坐标点形成有效路径时,记录为上一个坐标点
                            lastDegList.add(deg)
                            true
                        }
                    } else {
                        closeList.add(d)
                        isSame = true
                    }
                }
                // å¦åˆ™è®¤ä¸ºåŒä¸€è·¯æ®µ
                else {
                    isSame = true
                }
            } else {
                isSame = true
            }
            if (!isSame)
                records.add(mutableListOf())
            records.last().add(d)
        }
        return records
    }
}
src/main/kotlin/com/flightfeather/uav/common/location/CoordinateUtil.kt
@@ -1,13 +1,12 @@
package com.flightfeather.uav.common.location
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.*
object CoordinateUtil {
    private const val Ea = 6378137 //赤道半径
    private const val Eb = 6356725 //极半径
    private const val EARTH_RADIUS = 6371000 // åœ°çƒåŠå¾„,单位为米
    /**
     * æ ¹æ®åæ ‡ç‚¹ã€è·ç¦»å’Œè§’度,获取另一个坐标
@@ -26,6 +25,33 @@
    }
    /**
     * è®¡ç®—坐标点p2相对于p1的方位角
     * @return è§’度
     */
    fun getAngle(lon1: Double, lat1: Double, lon2: Double, lat2: Double): Double {
        val deg2rad = PI / 180
        val dlat = (lat2 - lat1) * deg2rad
        val dlon = (lon2 - lon1) * deg2rad
        val y = sin(dlon) * cos(lat2 * deg2rad)
        val x = cos(lat1 * deg2rad) * sin(lat2 * deg2rad) - sin(lat1 * deg2rad) * cos(lat2 * deg2rad) * cos(dlon)
        val angel = atan2(y, x)
        return (angel * 180 / PI + 360) % 360
    }
    /**
     * è®¡ç®—坐标点之间距离
     */
    fun calculateDistance(lon1: Double, lat1: Double, lon2: Double, lat2: Double): Double {
        val dLat = Math.toRadians(lat2 - lat1)
        val dLon = Math.toRadians(lon2 - lon1)
        val a = (sin(dLat / 2) * sin(dLat / 2)
                + (cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2))
                * sin(dLon / 2) * sin(dLon / 2)))
        val c = 2 * atan2(sqrt(a), sqrt(1 - a))
        return EARTH_RADIUS * c
    }
    /**
     * çº¬åº¦ç›¸åŒæ—¶
     * è·ç¦»è½¬æ¢ä¸ºç»åº¦
     */
src/main/kotlin/com/flightfeather/uav/lightshare/service/RealTimeDataService.kt
@@ -14,6 +14,8 @@
    fun getNextData(deviceCode: String, updateTime: String, page: Int?, perPage: Int?): BaseResponse<List<DataVo>>
    fun getSegmentData(missionCode: String): List<List<DataVo>>
    fun importData(file: MultipartFile): BaseResponse<DataImportResult>
    fun importJinanData(code:String, file: MultipartFile): DataImportResult
src/main/kotlin/com/flightfeather/uav/lightshare/service/impl/RealTimeDataServiceImpl.kt
@@ -6,8 +6,11 @@
import com.flightfeather.uav.common.utils.ExcelUtil
import com.flightfeather.uav.common.utils.FileExchange
import com.flightfeather.uav.biz.dataprocess.AverageUtil
import com.flightfeather.uav.biz.dataprocess.TrackSegment
import com.flightfeather.uav.domain.entity.*
import com.flightfeather.uav.domain.mapper.*
import com.flightfeather.uav.domain.repository.MissionRep
import com.flightfeather.uav.domain.repository.RealTimeDataRep
import com.flightfeather.uav.lightshare.bean.*
import com.flightfeather.uav.lightshare.service.RealTimeDataService
import com.flightfeather.uav.model.epw.EPWDataPrep
@@ -40,6 +43,8 @@
    private val realTimeDataGridOptMapper: RealTimeDataGridOptMapper,
    private val realTimeDataGridMinMapper: RealTimeDataGridMinMapper,
    private val missionMapper: MissionMapper,
    private val missionRep: MissionRep,
    private val realTimeDataRep: RealTimeDataRep,
) : RealTimeDataService {
    @Value("\${filePath}")
@@ -181,6 +186,12 @@
        example.orderBy("dataTime")
    }
    override fun getSegmentData(missionCode: String): List<List<DataVo>> {
        val mission = missionRep.findOne(missionCode) ?: throw BizException("任务不存在")
        val data = realTimeDataRep.fetchData(mission)
        return TrackSegment.segmentWithRoad(data).map { it.map { b -> b.toDataVo() } }
    }
    override fun importData(file: MultipartFile): BaseResponse<DataImportResult> {
        val f = ByteArrayInputStream(file.bytes)
        fileExchange.exchangeBoatData("0c0000000001", f).forEach {
src/main/kotlin/com/flightfeather/uav/lightshare/web/RealTimeDataController.kt
@@ -38,7 +38,7 @@
        @RequestPart("excel") file: MultipartFile,
    ) = realTimeDataService.importData(file)
    @ApiOperation(value = "导入静安区生态环境监测站的走行数据")
    @ApiOperation(value = "导入静安区生态环境监测站的走航数据")
    @PostMapping("/import/jinan")
    fun importJinanData(
        @ApiParam("设备id") @RequestParam("code") code: String,
@@ -48,4 +48,10 @@
    @ApiOperation(value = "下载静安区生态环境监测站走行数据导入模板")
    @PostMapping("/import/jinan/download/template")
    fun downloadTemplate(@ApiIgnore response: HttpServletResponse) = realTimeDataService.downloadTemplate(response)
    @ApiOperation(value = "获取按照路段分割的走航数据")
    @GetMapping("/sec/segment")
    fun getSegmentData(
        @ApiParam("任务id") @RequestParam("missionCode") missionCode: String,
    ) = resPack { realTimeDataService.getSegmentData(missionCode) }
}