4. 關鍵算法
生成行車軌跡的算法描述:對於每個過車agent,會隨機選擇一個車牌,隨機選擇一個卡口作爲起始卡口,生成一個行車軌跡。在所行駛的座標範圍內,在某個卡口點位可選擇的行駛方向如下圖所示:
由圖4可知,在不同的座標可選擇的行駛方向也有不同,可選擇4個方向,也可能選擇3個方向,也可能選擇2個方向。行車軌跡生成算法需要根據當前卡口座標,在候選方向集合中隨機選擇一個方向來模擬行車。
對於每個行車軌跡的長度,即經過的卡口點的數量,可隨機生成,建議最大距離爲行駛範圍矩形對角線的長度。
行車軌跡模擬算法如下所示:
名稱:模擬行車軌跡
輸入參數:無
輸出參數:無
過程:
//隨機選擇一個卡口
startTollgate = getRandomTollgate();
//隨機選擇一個車牌並鎖定該車牌
plate = getRandomPlate();
//將起始過車寫入過車數據流
motorvehicle = createMotorvehicle(startTollgate, plate);
writeMotorvehicleDatastream(motorvehicle);
//隨機生成行駛長度
distance = getRandomDistance();
//隨機生成行駛速度,在20~80中選個隨機數
speed = getRandomSpeed();
//計算模擬的每條過車記錄,並寫入過車數據流中
currentTollgate = startTollgate;
For(int i=0; i< distance; i++){
//根據當前卡口能走的方向計算出隨機行駛方向
direction = simuRandomDirection(currentTollgate);
//根據速度計算出經過下一卡口的過車時間
plate.lasttime = calcPassTime(speed);
//根據當前卡口和行駛方向計算出下一卡口
nextTollgate = calcNextTollgate(currentTollgate, direction);
//將模擬的過車寫入過車數據流
motorvehicle = createMotorvehicle(nextTollgate, plate);
writeMotorvehicleDatastream(motorvehicle);
//進入下一次循環
currentTollgate = nextTollgate
}
5. 運行方式
目前我們的視圖數據存儲使用hdfs和hive,建表工具使用iceberg。因此我們寫了一個flink job來寫入到iceberg的過車表中。flink job中自定義生成過車數據的source,過車寫入datastream中,最後寫入爲iceberg表的sink中。過車agent的source代碼如下:
/**
* Title: CommonMTAgent.java
* Description: 通用過車agent
* author zhang.kai
* Copyright: Copyright (c) 2025
* Company: www.kedacom.com
* date: 2021年7月21日-下午2:37:32
* version: V1.0
*/
package com.kedacom.simumotor.agent;
import java.io.Serializable;
import java.util.Date;
import org.apache.flink.streaming.api.functions.source.SourceFunction.SourceContext;
import org.apache.flink.table.data.GenericRowData;
import org.apache.flink.table.data.RowData;
import com.kedacom.simumotor.config.SimuConfig;
import com.kedacom.simumotor.pojo.Motorvehicle;
import com.kedacom.simumotor.pojo.Plate;
import com.kedacom.simumotor.pojo.SimuTollgate;
import com.kedacom.simumotor.pool.PoolManager;
import com.kedacom.simumotor.util.MotorVehicleUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CommonMTAgent implements Runnable, Serializable {
/**
*
*/
private static final long serialVersionUID = -183630777130253681L;
private SourceContext<RowData> sc;
public String name;
public CommonMTAgent() {
}
public CommonMTAgent(SourceContext<RowData> ctx) {
sc = ctx;
}
public void start() {
startSimuData();
}
@Override
public void run() {
log.info("************** plateLastAppearTime:{}",new Date(SimuConfig.plateLastAppearTime).toString());
long count = 0;
while (!SimuConfig.ifstop) {
try {
// 獲取隨機卡口和車牌
SimuTollgate tollgate = PoolManager.getRandomTollgate();
Plate plate = PoolManager.getRandomPlate();
// 生成行車軌跡
synchronized (plate) {
if (plate.getPlateLastTime() < System.currentTimeMillis()) {
plate.setPlateLastTime(System.currentTimeMillis());
}
// 在上次出現的時間上加上間隔
plate.setPlateLastTime(plate.getPlateLastTime() + SimuConfig.APPEAR_DELAY * 1000);
writeMotorvehicle2Stream(tollgate, plate);
count++;
// 生成隨機軌跡
int pathLength = MotorVehicleUtils.getRandomPathLength();
for (int i = 0; i < pathLength; i++) {
tollgate = MotorVehicleUtils.getNextTollgate(tollgate);
if (null == tollgate) {
break;
}
MotorVehicleUtils.setNextAppearTime(plate);
writeMotorvehicle2Stream(tollgate, plate);
count++;
}
}
// 到達批量寫入,進行計數
if (count >= SimuConfig.BATCH_WRITE_SIZE) {
log.info("batchWriteMv:{}", count);
// Thread.sleep(10000);
count = 0;
}
} catch (Exception e) {
log.error("XXXXXX CommonMTAgent run error", e);
}
}
}
/**
* 啓動模擬數據線程
*/
private void startSimuData() {
Thread agentThread = new Thread(this);
agentThread.setName("SimuThread-" + name);
agentThread.start();
}
/**
* 將生成的motorvehicle寫入數據流中
*
* @param tollgate
* @param plate
* @param metricqueue
*/
private void writeMotorvehicle2Stream(SimuTollgate tollgate, Plate plate) {
Motorvehicle mv = MotorVehicleUtils.getMotorvehicle(tollgate, plate);
RowData row = transformMv(mv);
sc.collect(row);
}
/**
* 將motorvehicle對象轉換爲
*
* @param mv
* @return
*/
private RowData transformMv(Motorvehicle mv) {
GenericRowData rowData = new GenericRowData(16);
long passtime = mv.getPassTime() / 1000;
int days = (int) (passtime / (60 * 60 * 24));
rowData.setField(0, days);
rowData.setField(1, MotorVehicleUtils.wrap(mv.getMotorVehicleID()));
rowData.setField(2, mv.getInfoKind());
rowData.setField(3, MotorVehicleUtils.wrap(mv.getTollgateId()));
rowData.setField(4, passtime);
rowData.setField(5, MotorVehicleUtils.wrap(mv.getDeviceId()));
rowData.setField(6, MotorVehicleUtils.wrap(mv.getStorageUrl1()));
rowData.setField(7, mv.getLeftTopX());
rowData.setField(8, mv.getLeftTopY());
rowData.setField(9, mv.getRightBtmX());
rowData.setField(10, mv.getRightBtmY());
rowData.setField(11, MotorVehicleUtils.wrap(mv.getHasPlate()));
rowData.setField(12, MotorVehicleUtils.wrap(mv.getPlateClass()));
rowData.setField(13, MotorVehicleUtils.wrap(mv.getPlateColor()));
rowData.setField(14, MotorVehicleUtils.wrap(mv.getPlateNo()));
rowData.setField(15, MotorVehicleUtils.wrap(mv.getVehicleColor()));
return rowData;
}
}
經過測試,在我們自己的flink集羣上,每小時能夠寫入約2億條過車數據,一天大約能夠寫入近50億過車數據。