| | |
| | | |
| | | excGroup = exceptionDataGroup().map { ExcGroup(it, e.first.main) } |
| | | avgPer = excGroup?.mapNotNull { it.per }?.average() |
| | | if (avgPer?.isNaN() == true) avgPer = .0 |
| | | avgRate = excGroup?.mapNotNull { it.rate }?.average() |
| | | if (avgRate?.isNaN() == true) avgRate = .0 |
| | | } |
| | | } |
| | | } |
| | |
| | | /** |
| | | * 允许接口返回的业务层面的错误 |
| | | */ |
| | | class BizException : Exception { |
| | | class BizException : RuntimeException { |
| | | constructor():super() |
| | | constructor(message: String) : super(message) |
| | | constructor(message: String, cause: Throwable?) : super(message, cause) |
| | |
| | | return gridDataMapper.insert(gridData) |
| | | } |
| | | |
| | | fun updateGridData(gridData: GridData): Int { |
| | | return gridDataMapper.updateByPrimaryKey(gridData) |
| | | } |
| | | |
| | | fun insertGridDataDetail(gridDataDetails: List<GridDataDetail?>): Int { |
| | | return gridDataDetailMapper.insertList(gridDataDetails) |
| | | } |
| | |
| | | else -> null |
| | | } |
| | | } |
| | | stMsgCache[key] = stMsgList |
| | | // Fixme 2026.1.21 溯源记录的缓存逻辑暂缺失,此处处理不恰当 |
| | | // stMsgCache[key] = stMsgList |
| | | } |
| | | // 筛选出异常数据PollutedClue中异常百分比大于minPer的 |
| | | return stMsgList.filter { |
| | |
| | | private val tmpVehicleDataList = mutableListOf<BaseRealTimeData>() |
| | | |
| | | // 走航监测校准系数 |
| | | private val calibrationMap = mutableMapOf<String, MutableMap<Int, Float>>() |
| | | private val calibrationMap = mutableMapOf<String?, MutableMap<Int, Float>>() |
| | | // 走航监测校准系数更新时间 |
| | | private var cUpdateTime = LocalDateTime.now() |
| | | // 走航监测校准系数更新时间间隔(分钟) |
| | | private val cInterval = 5L |
| | | private val cInterval = 1L |
| | | |
| | | override fun saveAirData(dataPackage: AirDataPackage): Int { |
| | | val data = RealTimeData().apply { |
| | |
| | | val d = vo.toBaseRealTimeData(RealTimeDataVehicle::class.java) |
| | | /***************************************************************************************************/ |
| | | // FIXME: 2021/10/27 车载监测部分因子量级调整 |
| | | calibration(d, UWDeviceType.VEHICLE) |
| | | calibration(d, d.deviceCode) |
| | | /***************************************************************************************************/ |
| | | realTimeDataVehicleMapper.insert(d) |
| | | res.add(d) |
| | |
| | | } |
| | | } |
| | | |
| | | private fun calibration(data: BaseRealTimeData, type: UWDeviceType) { |
| | | private fun calibration(data: BaseRealTimeData, deviceCode: String?) { |
| | | //1. 校准系数按照一定时间间隔进行刷新 |
| | | val now = LocalDateTime.now() |
| | | if (calibrationMap.isEmpty() || now.minusMinutes(cInterval).isAfter(cUpdateTime)) { |
| | | if (calibrationMap[deviceCode].isNullOrEmpty() || now.minusMinutes(cInterval).isAfter(cUpdateTime)) { |
| | | cUpdateTime = now |
| | | calibrationMap[type.value] = mutableMapOf() |
| | | calibrationMap[deviceCode] = mutableMapOf() |
| | | factorCalibrationMapper.selectByExample(Example(FactorCalibration::class.java).apply { |
| | | createCriteria().andEqualTo("deviceType", type.value) |
| | | createCriteria().andEqualTo("deviceType", deviceCode) |
| | | }).forEach { |
| | | calibrationMap[type.value]?.put(it.factorId, it.factorScale) |
| | | calibrationMap[deviceCode]?.put(it.factorId, it.factorScale) |
| | | } |
| | | } |
| | | //2. 根据校准系数计算 |
| | | calibrationMap[type.value]?.let{ |
| | | calibrationMap[deviceCode]?.let { |
| | | data.voc = data.voc?.times(it[FactorType.VOC.value] ?: 1f) |
| | | data.co = data.co?.times(it[FactorType.CO.value] ?: 1f) |
| | | data.pm25 = data.pm25?.times(it[FactorType.PM25.value] ?: 1f) |
| | |
| | | return null |
| | | } |
| | | |
| | | fun setFactorData(type: FactorType, value: Double?) { |
| | | if (values == null) throw IllegalStateException(this.javaClass.name + ": 监测数据数组为null") |
| | | for (d in values!!) { |
| | | if (d.factorName == type.name) { |
| | | d.factorData = value |
| | | } |
| | | } |
| | | } |
| | | |
| | | fun toRowContent(): Array<Any> { |
| | | val row = mutableListOf<Any>() |
| | | row.add(deviceCode ?: "") |
| | |
| | | import com.flightfeather.uav.domain.entity.Mission |
| | | import com.flightfeather.uav.domain.entity.SceneInfo |
| | | import com.flightfeather.uav.lightshare.bean.AreaVo |
| | | import com.flightfeather.uav.lightshare.bean.DataHead |
| | | import com.flightfeather.uav.lightshare.bean.GridDataDetailMixVo |
| | | import com.flightfeather.uav.lightshare.eunm.PollutionDegree |
| | | import com.flightfeather.uav.socket.eunm.FactorType |
| | |
| | | * @param minPer 最小污染百分比,用于筛选异常数据点(可选) |
| | | * @return 历史污染溯源结果的字符串表示(具体格式需参考实现类) |
| | | */ |
| | | fun fetchHistory(missionCode: String, minPer: Double?): String |
| | | fun fetchHistory(missionCode: String, minPer: Double?, page: Int?, perPage: Int?): Pair<DataHead, String> |
| | | |
| | | /** |
| | | * 生成走航任务汇总统计 |
| | |
| | | import com.flightfeather.uav.domain.mapper.MissionMapper |
| | | import com.flightfeather.uav.domain.repository.* |
| | | import com.flightfeather.uav.lightshare.bean.AreaVo |
| | | import com.flightfeather.uav.lightshare.bean.DataHead |
| | | import com.flightfeather.uav.lightshare.bean.GridDataDetailMixVo |
| | | import com.flightfeather.uav.lightshare.bean.SourceTraceMsgVo |
| | | import com.flightfeather.uav.lightshare.eunm.PollutionDegree |
| | | import com.flightfeather.uav.lightshare.eunm.SceneType |
| | | import com.flightfeather.uav.lightshare.service.DataAnalysisService |
| | | import com.flightfeather.uav.lightshare.service.SatelliteDataCalculateService |
| | | import com.flightfeather.uav.socket.eunm.FactorType |
| | | import com.flightfeather.uav.socket.sender.MsgType |
| | | import com.github.pagehelper.PageHelper |
| | | import org.springframework.stereotype.Service |
| | | import java.util.* |
| | | |
| | |
| | | * @return 历史污染溯源结果的JSON字符串,具体格式由sourceTraceRep实现决定 |
| | | * @throws BizException 当走航任务不存在时抛出 |
| | | */ |
| | | override fun fetchHistory(missionCode: String, minPer: Double?): String { |
| | | override fun fetchHistory(missionCode: String, minPer: Double?, page: Int?, perPage: Int?): Pair<DataHead, String> { |
| | | val mission = missionRep.findOne(missionCode) ?: throw BizException("走航任务不存在") |
| | | |
| | | val p = if (page != null && perPage != null) { |
| | | PageHelper.startPage<SourceTraceMsgVo>(page, perPage) |
| | | } else { |
| | | null |
| | | } |
| | | val res = sourceTraceRep.fetchList(mission.deviceCode, mission.startTime, mission.endTime, minPer = minPer ?: 0.5) |
| | | return GsonUtils.gson.toJson(res) |
| | | return DataHead(p?.pageNum ?: 1, p?.pages ?: 1) to GsonUtils.gson.toJson(res) |
| | | } |
| | | |
| | | /** |
| | |
| | | val data = realTimeDataRep.fetchData(mission) |
| | | mission.kilometres = MissionUtil.calKilometres(data).toFloat() |
| | | // todo: 计算走航任务所在中心区域 |
| | | // mission.region = MissionUtil.calRegion(data) |
| | | mission.region = MissionUtil.calRegion(data) |
| | | return updateMission(mission) |
| | | } |
| | | } |
| | |
| | | // 查询走航任务及对应走航监测数据 |
| | | val mission = missionRep.findOne(missionCode) ?: throw BizException("任务不存在") |
| | | val data = realTimeDataRep.fetchData(mission) |
| | | if (data.isEmpty()) throw BizException("没有走航数据,无法生成走航网格") |
| | | |
| | | // 查找是否已有走航融合记录 |
| | | val oldGridDataList = satelliteGridRep.fetchGridData(GridData().apply { |
| | |
| | | |
| | | return gridDataDetailList |
| | | } else { |
| | | satelliteGridRep.updateGridData(gridData) |
| | | val oldGridData = oldGridDataList.first() |
| | | val oldGridDataDetailList = satelliteGridRep.fetchGridDataDetail(oldGridData?.id, oldGridData?.groupId) |
| | | // 查询网格单元格信息 |
| | |
| | | } |
| | | } catch (e: BizException) { |
| | | BaseResponse(false, message = e.message ?: "") |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | BaseResponse(false, message = e.message ?: "服务器出现内部错误") |
| | | } |
| | | } |
| | |
| | | fun fetchHistory( |
| | | @ApiParam("走航任务编号") @RequestParam missionCode: String, |
| | | @ApiParam("最小污染百分比,用于筛选异常数据点(可选)") @RequestParam(required = false) minPer: Double? = null, |
| | | ) = resPack { dataAnalysisService.fetchHistory(missionCode, minPer) } |
| | | @RequestParam(value = "page", required = false) page: Int?, |
| | | @RequestParam(value = "perPage", required = false) perPage: Int?, |
| | | ) = resPack { dataAnalysisService.fetchHistory(missionCode, minPer, page, perPage) } |
| | | |
| | | @ApiOperation(value = "生成走航任务汇总统计") |
| | | @PostMapping("/report/missionSummary") |
| | |
| | | import kotlin.math.min |
| | | import kotlin.math.round |
| | | import kotlin.math.sqrt |
| | | import kotlin.time.times |
| | | |
| | | /** |
| | | * 数据平滑预处理 |
| | |
| | | i = 0 |
| | | } |
| | | while (i < mDataList.size) { |
| | | // 针对每个监测因子,分别做数据平滑处理 |
| | | for (y in mDataList[i].values?.indices ?: 0..0) { |
| | | val it = mDataList[i].values?.get(y) ?: continue |
| | | val vMax = FactorType.getVMin(it.factorName) ?: continue |
| | |
| | | } |
| | | } |
| | | |
| | | // 根据物理规律,剔除或修正不合理的数据 |
| | | val data = mDataList[i] |
| | | // 1. PM2.5 应该始终小于PM10 |
| | | val pm25 = data.getFactorData(FactorType.PM25) |
| | | val pm10 = data.getFactorData(FactorType.PM10) |
| | | if (pm25 != null && pm10 != null) { |
| | | // 若pm2.5大于pm10 |
| | | if (pm25 >= pm10){ |
| | | val lastIndex = i - 1 |
| | | // 则将pm2.5修改为前一个数据的值 |
| | | if (lastIndex >= 0) { |
| | | data.setFactorData(FactorType.PM25, mDataList[lastIndex].getFactorData(FactorType.PM25)) |
| | | } else { |
| | | if (lastData.isEmpty()) { |
| | | // 没有历史数据时,修改为pm10的80%(后续待优化比例 2026.3.6) |
| | | data.setFactorData(FactorType.PM25, data.getFactorData(FactorType.PM10)?.times(.8)) |
| | | } else { |
| | | data.setFactorData(FactorType.PM25, lastData.last().getFactorData(FactorType.PM25)) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | i++ |
| | | } |
| | | |
| | |
| | | fun saveToDataBase(dataPackage: AirDataPackage): List<BaseRealTimeData>? { |
| | | when (dataPackage.commandUnit) { |
| | | AirCommandUnit.AirData.value -> { |
| | | // 存储前判断数据是否有效 |
| | | if (!isValid(dataPackage)) return null |
| | | // 以json格式存储原始数据 |
| | | airDataRep.saveAirData(dataPackage) |
| | | // 进行预处理后,存储至对应数据表 |
| | |
| | | |
| | | return sb.toString() |
| | | } |
| | | |
| | | /** |
| | | * 数据有效性判断 |
| | | */ |
| | | private fun isValid(dataPackage: AirDataPackage): Boolean { |
| | | if (dataPackage.dataTime == null) return false |
| | | val check1 = dataPackage.dataTime!!.time < Date().time |
| | | return check1 |
| | | } |
| | | } |
| | |
| | | spring: |
| | | datasource: |
| | | # 线上服务器 47 |
| | | # url: jdbc:mysql://localhost:3306/dronemonitor?serverTimezone=Asia/Shanghai&prepStmtCacheSize=517&cachePrepStmts=true&autoReconnect=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false |
| | | # username: dronemonitor |
| | | # password: dronemonitor_hackxrnomxm |
| | | |
| | | # 线上服务器 114 |
| | | url: jdbc:mysql://localhost:3306/dronemonitor?serverTimezone=Asia/Shanghai&prepStmtCacheSize=517&cachePrepStmts=true&autoReconnect=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false |
| | | username: dronemonitor |
| | | password: dronemonitor_hackxrnomxm |
| | | |
| | | # 线上服务器 114 |
| | | # url: jdbc:mysql://localhost:3306/dronemonitor?serverTimezone=Asia/Shanghai&prepStmtCacheSize=517&cachePrepStmts=true&autoReconnect=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false |
| | | # username: dronemonitor |
| | | # password: dronemonitor_hackxrnomxm |
| | | |
| | | springfox: |
| | | documentation: |
| | | swagger: |
| | |
| | | @Test |
| | | fun autoSourceTrace() { |
| | | val sourceTraceController = SourceTraceController(sceneInfoRep, sourceTraceRep, true) |
| | | val startTime = LocalDateTime.of(2025, 11, 19, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant() |
| | | val startTime = LocalDateTime.of(2025, 10, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant() |
| | | // val endTime = LocalDateTime.of(2025, 11, 19, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant() |
| | | // val startTime = LocalDateTime.of(2025, 11, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant() |
| | | val endTime = LocalDateTime.of(2025, 12, 31, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant() |
| | |
| | | |
| | | @Test |
| | | fun deleteSourceTrace() { |
| | | val startTime = LocalDateTime.of(2025, 11, 19, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant() |
| | | val startTime = LocalDateTime.of(2025, 12, 18, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant() |
| | | // val endTime = LocalDateTime.of(2025, 11, 19, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant() |
| | | // val startTime = LocalDateTime.of(2025, 11, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant() |
| | | val endTime = LocalDateTime.of(2025, 12, 31, 23, 59, 59).atZone(ZoneId.systemDefault()).toInstant() |
| | |
| | | @Test |
| | | fun fetchData() { |
| | | val mission = Mission().apply { |
| | | missionCode = "SHJA-20240813" |
| | | missionCode = "20260119" |
| | | deviceType = "0a" |
| | | deviceCode = "TX105" |
| | | startTime = Date.from(LocalDateTime.of(2024, 8, 13, 11, 30, 0, 0).atZone(ZoneId.systemDefault()) |
| | | startTime = Date.from(LocalDateTime.of(2026, 1, 19, 8, 30, 0, 0).atZone(ZoneId.systemDefault()) |
| | | .toInstant()) |
| | | endTime = Date.from(LocalDateTime.of(2024, 8, 13, 17, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()) |
| | | endTime = Date.from(LocalDateTime.of(2026, 1, 19, 16, 30, 0, 0).atZone(ZoneId.systemDefault()).toInstant()) |
| | | districtName = "静安区" |
| | | } |
| | | val date = LocalDateTime.of(2024, 8, 13, 15, 43, 0, 0) |
| | | val date = LocalDateTime.of(2026, 1, 19, 14, 0, 0, 0) |
| | | val res = ShenXinService.fetchData(mission, date) |
| | | println(res) |
| | | } |
| | |
| | | |
| | | @Test |
| | | fun fetchHistory() { |
| | | dataAnalysisService.fetchHistory("SH-CN-20250723(01)", .0) |
| | | dataAnalysisService.fetchHistory("SH-CN-20260121", .0, 1, 10000) |
| | | } |
| | | |
| | | @Test |
| | |
| | | @Test |
| | | fun calMissionInfo() { |
| | | missionMapper.selectByExample(Example(Mission::class.java).apply { |
| | | createCriteria().andBetween("startTime", "2025-12-11 00:00:00", "2025-12-11 23:59:59") |
| | | createCriteria().andBetween("startTime", "2026-01-29 00:00:00", "2026-12-31 23:59:59") |
| | | }).forEach {mission -> |
| | | mission?.let { missionService.calMissionInfo(it.missionCode) } |
| | | Thread.sleep(1000) |