public abstract class AbstractPeopleCountingHandler implements AbstractSmartDataHandler {
@Lazy
@Autowired
protected PeopleCountingCache peopleCountingCache;
@Lazy
@Autowired
protected PeopleCountingResultCache peopleCountingResultCache;
@Autowired
private AiSwitchConfig aiSwitchConfig;
@Autowired
private TrajectoryCache trajectoryCache;
@Autowired
private DeviceInfoContextCacheService deviceInfoContextCacheService;
@Value("${peopleCounting.trajectory.algorithm:multi_segments}")
private String trajectoryAlgorithm = "multi_segments";
@Value("${peopleCounting.trajectory.sufficientDistanceTimes:2}")
private int sufficientDistanceTimes = 2;
protected static final Set<Long> NEED_LOG_DEVICE_IDS = new HashSet<>();
private static final DynamicObjectLogger<Long> log = new DynamicObjectLogger<Long>(
AbstractPeopleCountingHandler.class) {
@Override
public boolean debugEnabled(Long deviceId) {
// todo
return true;
// return NEED_LOG_DEVICE_IDS.contains(deviceId);
}
};
protected abstract AiCustomThreadPool getThreadPool();
public abstract <T extends DeviceEntity> Collection<PeopleCountingConfigEntity> getRealDeviceConfigs(
T deviceEntity);
protected abstract Boolean isBusinessTime(PeopleCountingConfigEntity deviceConfig);
protected abstract Boolean isBusinessTime(HistoricalEntity history);
public static void updateNeedLogDeviceIds(Collection<Long> deviceIds) {
NEED_LOG_DEVICE_IDS.clear();
if (CollectionUtil.isNotEmpty(deviceIds)) {
NEED_LOG_DEVICE_IDS.addAll(deviceIds);
}
}
@PostConstruct
public void init() {
AiModule module = getModule();
log.info(1L, "peoples counting handler init, module {}, enabled {}.",
module, aiSwitchConfig.isAiModuleEnabled(module));
if (aiSwitchConfig.isAiModuleEnabled(module)) {
AiSmartDataProcessor.registerHandler(module, this);
}
}
@Trace
@Override
public void handleSmartData(DeviceInfoContext deviceInfo, SmartDataFrame frame) {
Integer threadPoolIndex = deviceInfo.getPassengerFlowStatisticsThreadPoolIndex();
Long deviceId = deviceInfo.getDeviceId();
if (Objects.isNull(threadPoolIndex)) {
int execute = getThreadPool().execute(deviceInfo.getKey(),
() -> doHandleSmartData(deviceInfo, frame));
log.info(deviceId, "ai thread pool index [passenger flow stat] {}, device id {}, dev id {}, channel {}.",
execute,
deviceId, CustomPIIMaskUtil.encrypt(deviceInfo.getDevId()), deviceInfo.getChannel());
deviceInfo.setPassengerFlowStatisticsThreadPoolIndex(execute);
} else {
getThreadPool().execute(threadPoolIndex,
() -> doHandleSmartData(deviceInfo, frame));
}
}
@Trace
private void doHandleSmartData(DeviceInfoContext deviceInfo, SmartDataFrame frame) {
log.info(123L, "frame is {}", frame);
if (!verified(deviceInfo, frame)) {
log.debug(deviceInfo.getDeviceId(), "invalid data, DeviceInfoContext: {}, SmartDataFrame: {}.", deviceInfo,
frame);
return;
}
Long deviceId = deviceInfo.getDeviceId();
DeviceEntity deviceEntity = DeviceEntity.getDeviceEntity(deviceInfo);
log.debug(deviceId, "deviceEntity is {}", deviceEntity);
Collection<PeopleCountingConfigEntity> deviceConfigs = getDeviceConfigs(deviceEntity);
if (CollectionUtil.isEmpty(deviceConfigs)) {
log.debug(deviceId, "device config is empty");
return;
}
log.info(deviceId, "deviceConfigs is {}", deviceConfigs);
List<DevicePointEntity> points = frame.getObjs().stream().map(DevicePointEntity::getDevicePointEntity)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (CollectionUtil.isEmpty(points)) {
log.debug(deviceId, "points is empty, frame: {}.", frame);
return;
}
Collection<ResultEntity> results = new HashSet<>();
long timeStamp = System.currentTimeMillis() / TimeUnit.MINUTES.toMillis(1);
for (PeopleCountingConfigEntity deviceConfig : deviceConfigs) {
// todo 并发处理
ResultEntity resultEntity = trajectoryRecognition(deviceEntity, deviceConfig, points);
log.debug(deviceId, "resultEntity is {}.", resultEntity);
if (Objects.nonNull(resultEntity)) {
resultEntity.setTimeMinute(timeStamp);
results.add(resultEntity);
}
}
if (CollectionUtil.isEmpty(results)) {
log.debug(deviceId, "results is empty, frame: {}.", frame);
return;
}
peopleCountingResultCache.offers(results);
}
public Optional<ResultEntity> handleDisappearsData(HistoricalEntity history) {
Long deviceId = history.getDeviceId();
CrossAreaWayEnum crossAreaWay = history.resetInside(sufficientDistanceTimes);
if (Objects.equals(crossAreaWay, CrossAreaWayEnum.USELESS)) {
return Optional.empty();
}
ResultEntity resultEntity = history.generateResultEntity();
resultEntity.setBusinessTime(isBusinessTime(history));
switch (crossAreaWay) {
case PASS_BY_STORE:
resultEntity.setPassBy(1);
return Optional.of(resultEntity);
case ENTER_STORE:
resultEntity.setEnter(1);
return Optional.of(resultEntity);
case LEAVE_STORE:
resultEntity.setCheckOut(1);
return Optional.of(resultEntity);
default:
log.debug(deviceId, "useless cross area way: {}", crossAreaWay);
}
return Optional.empty();
}
public Optional<ResultEntity> handleDisappearsData(String key, Line start, Line end) {
Optional<ResultEntity> resultEntity = PeopleCountingCache.parseKey(key);
if (!resultEntity.isPresent()) {
return Optional.empty();
}
ResultEntity result = resultEntity.get();
Long deviceId = result.getDeviceId();
int passBy = 0;
int enter = 0;
int checkOut = 0;
try {
CrossAreaWayEnum crossAreaWay = identifyDisappearsTrajectory(key, start, end);
log.debug(deviceId, "cross area way: {}, key: {}, start: {}, end: {}.", crossAreaWay, key, start, end);
switch (crossAreaWay) {
case PASS_BY_STORE:
passBy++;
break;
case ENTER_STORE:
enter++;
break;
case LEAVE_STORE:
checkOut++;
break;
default:
log.debug(deviceId, "useless cross area way: {}", crossAreaWay);
}
} catch (Exception e) {
log.debug(deviceId, "trajectory recognition error, key: {}, start: {}, end: {}.", key, start, end, e);
}
if (passBy == 0 && enter == 0 && checkOut == 0) {
return Optional.empty();
}
OperationResponse<DeviceInfoContext> deviceInfoContextByDeviceId = deviceInfoContextCacheService.getDeviceInfoContextByDeviceId(
deviceId);
if (deviceInfoContextByDeviceId.isError() || Objects.isNull(deviceInfoContextByDeviceId.getResult())) {
log.debug(deviceId, "device info context is null, device id: {}.", deviceId);
return Optional.empty();
}
AiModule module = getModule();
result.setModule(module);
result.setDeviceEntity(DeviceEntity.getDeviceEntity(deviceInfoContextByDeviceId.getResult()));
Optional<PeopleCountingConfigEntity> deviceConfigOptional = peopleCountingCache.getConfigOptional(module,
deviceId, result.getConfigId());
if (!deviceConfigOptional.isPresent()) {
return Optional.empty();
}
PeopleCountingConfigEntity deviceConfig = deviceConfigOptional.get();
result.setConfigId(deviceConfig.getConfigId());
result.setBusinessTime(isBusinessTime(deviceConfig));
result.setPassBy(passBy);
result.setEnter(enter);
result.setCheckOut(checkOut);
result.setTimeMinute(System.currentTimeMillis() / TimeUnit.MINUTES.toMillis(1));
log.debug(deviceId, "trajectory recognition result: {}.", result);
return Optional.of(result);
}
public CrossAreaWayEnum identifyDisappearsTrajectory(String key, Line start, Line end) {
HistoricalEntity historical = peopleCountingCache.getHistorical(getModule(), key);
if (Objects.isNull(historical)) {
return CrossAreaWayEnum.USELESS;
}
List<Point> polygon = historical.getCurrentConfig().getPolygon();
Point outside = polygon.get(polygon.size() - 2);
if (Boolean.TRUE.equals(historical.getCurrentConfig().getEnterFromA2B())) {
outside = polygon.get(0);
}
Point enter;
IntersectionLineDTO enterIntersectionLine = historical.getEnterIntersectionLine();
if (Objects.isNull(enterIntersectionLine)) {
enterIntersectionLine = GeometryUtils.findRayIntersection(historical.getCurrentConfig().getPolygon(),
start.reverse());
}
enter = enterIntersectionLine.getIntersection();
IntersectionLineDTO leaveIntersectionLine = historical.getLeaveIntersectionLine();
if (Objects.isNull(leaveIntersectionLine)) {
leaveIntersectionLine = GeometryUtils.findRayIntersection(
historical.getCurrentConfig().getPolygon(), end);
}
Point leave = leaveIntersectionLine.getIntersection();
Line cuttingLine = historical.getCurrentConfig().getCuttingLine();
CrossAreaWayEnum crossAreaWay = getCrossAreaWay(enter,
leave, cuttingLine,
outside);
log.debug(12L,
"[PassengerFlowStatistics] getCrossAreaWay enter = {}, leave = {}, cuttingLine = {}, outside = {}.",
enter, leave, cuttingLine, outside);
reset(historical);
return crossAreaWay;
}
private boolean verified(DeviceInfoContext deviceInfo, SmartDataFrame frame) {
if (Objects.isNull(deviceInfo) || Objects.isNull(frame)) {
return false;
}
if (Objects.isNull(deviceInfo.getDeviceId())) {
return false;
}
if (CollectionUtil.isEmpty(frame.getObjs())) {
return false;
}
return true;
}
private Collection<PeopleCountingConfigEntity> getDeviceConfigs(DeviceEntity deviceEntity) {
return peopleCountingCache.getDeviceConfigs(deviceEntity, getModule());
}
@Trace
private ResultEntity trajectoryRecognition(DeviceEntity deviceEntity, PeopleCountingConfigEntity deviceConfig,
List<DevicePointEntity> points) {
Long deviceId = deviceConfig.getDeviceId();
int passBy = 0;
int enter = 0;
int checkOut = 0;
for (DevicePointEntity point : points) {
HistoricalEntity historical = obtainHistorical(deviceEntity, deviceConfig, point.getObjectId());
log.debug(deviceId, "historical is {}", historical);
if (Objects.isNull(historical)) {
continue;
}
try {
CrossAreaWayEnum crossAreaWay = identifyTrajectory(historical, point);
log.debug(deviceId, "cross area way: {}, historical: {}, point: {}.", crossAreaWay, historical, point);
switch (crossAreaWay) {
case PASS_BY_STORE:
passBy++;
break;
case ENTER_STORE:
enter++;
break;
case LEAVE_STORE:
checkOut++;
break;
default:
log.debug(deviceId, "useless cross area way: {}", crossAreaWay);
}
} catch (Exception e) {
log.debug(deviceId, "trajectory recognition error, historical: {}, point: {}.", historical, point, e);
}
}
if (passBy == 0 && enter == 0 && checkOut == 0) {
return null;
}
ResultEntity result = new ResultEntity();
result.setModule(getModule());
result.setDeviceEntity(deviceEntity);
result.setConfigId(deviceConfig.getConfigId());
result.setBusinessTime(isBusinessTime(deviceConfig));
result.setPassBy(passBy);
result.setEnter(enter);
result.setCheckOut(checkOut);
log.debug(deviceId, "trajectory recognition result: {}.", result);
return result;
}
private HistoricalEntity obtainHistorical(DeviceEntity deviceEntity, PeopleCountingConfigEntity deviceConfig,
Integer objectId) {
return peopleCountingCache.getHistorical(deviceEntity, deviceConfig.getConfigId(), objectId,
getModule());
}
private CrossAreaWayEnum identifyTrajectory(HistoricalEntity historical, DevicePointEntity point) {
Long deviceId = historical.getDeviceId();
log.debug(deviceId,
"[PassengerFlowStatistics] Start processing passenger flow statistics,HistoricalEntity = {}, current = {}.",
JsonUtils.bean2Json(historical), JsonUtils.bean2Json(point));
Point current = point.getPoint();
if (Objects.isNull(current)) {
return CrossAreaWayEnum.USELESS;
}
historical.setPrevious(historical.getCurrent());
historical.setPreviousInside(historical.getCurrentInside());
historical.setCurrent(current);
historical.setCurrentInside(getPosition(historical.getDeviceConfigs(), current));
CrossAreaWayEnum crossAreaWayEnum = CrossAreaWayEnum.USELESS;
log.debug(deviceId, "[PassengerFlowStatistics] The current node is located at {}",
historical.getCurrentInside());
if (Objects.nonNull(historical.getCurrentInside())) {
DeviceConfigDTO currentDeviceConfigDTO = historical.getDeviceConfigs().get(historical.getCurrentInside());
if (point.getConfidence() + currentDeviceConfigDTO.getSensitivity() < 100) {
log.debug(deviceId,
"[PassengerFlowStatistics] The node confidence does not meet the requirements, confidence = {}, Sensitivity = historical.currentDeviceConfigDTO.getSensitivity()",
historical.getCurrentInside());
// 越不灵敏越不能接受置信度小的数据,如此灵敏度和置信度都很小的数据,丢弃
historical.setCurrent(historical.getPrevious());
historical.setCurrentInside(historical.getPreviousInside());
return CrossAreaWayEnum.USELESS;
}
processTrajectoryCache(historical, point.getObjectId(), current, point.getBoxHeight());
historical.setCurrentConfig(currentDeviceConfigDTO);
if (Objects.isNull(historical.getEnterIntersectionLine()) && (
Objects.equals(historical.getCurrentInside(), historical.getPreviousInside())
|| Objects.isNull(historical.getPrevious()))) {
log.debug(deviceId, "[PassengerFlowStatistics] There is no trajectory entering the area.");
//判断是否在边界上,在边界上即为进入线
IntersectionLineDTO enterIntersectionLine = GeometryUtils.findIntersection(
currentDeviceConfigDTO.getPolygon(),
current);
log.debug(deviceId,
"[PassengerFlowStatistics] The result of judging whether current node is on the boundary is {}",
enterIntersectionLine);
if (Objects.isNull(enterIntersectionLine)) {
// 没有进入边界的点,凭空出现在了区域内
return CrossAreaWayEnum.USELESS;
}
historical.setEnterIntersectionLine(enterIntersectionLine);
}
// 位于区域内,那离开的位置就得重新计算
historical.setLeaveIntersectionLine(null);
historical.setCompleteLeave(false);
} else {
// 位于区域外,但是还没完全进入,那就得重新计算进入位置
if (Boolean.FALSE.equals(historical.getCompleteEnter())) {
historical.setEnterIntersectionLine(null);
historical.setCompleteEnter(false);
}
crossAreaWayEnum = historical.resetInside(sufficientDistanceTimes);
}
if (Objects.nonNull(historical.getCurrentInside()) && Objects.isNull(historical.getPreviousInside())) {
// 进入区域
if (Objects.isNull(historical.getPrevious())) {
return CrossAreaWayEnum.USELESS;
}
historical.setEnterIntersectionLine(GeometryUtils.findIntersection(
historical.getCurrentConfig().getPolygon(),
new Line(current, historical.getPrevious())));
log.debug(deviceId, "[PassengerFlowStatistics] The target enters the area from {}",
historical.getEnterIntersectionLine());
} else if (Objects.isNull(historical.getCurrentInside()) && Objects.nonNull(historical.getPreviousInside())) {
if (Objects.isNull(historical.getPrevious())) {
return CrossAreaWayEnum.USELESS;
}
historical.setLeaveIntersectionLine(GeometryUtils.findIntersection(
historical.getCurrentConfig().getPolygon(),
new Line(current, historical.getPrevious())));
log.debug(deviceId, "[PassengerFlowStatistics] The target leaves the area from {}",
historical.getLeaveIntersectionLine());
}
if (Objects.nonNull(historical.getEnterIntersectionLine()) && !Boolean.TRUE.equals(
historical.getCompleteEnter())) {
historical.setCompleteEnter(
GeometryUtils.distanceToLineSegment(historical.getEnterIntersectionLine().getLine(), current)
> point.getHalfWidth());
}
if (Objects.nonNull(historical.getLeaveIntersectionLine()) && !Boolean.TRUE.equals(
historical.getCompleteLeave())) {
historical.setCompleteLeave(
GeometryUtils.distanceToLineSegment(historical.getLeaveIntersectionLine().getLine(), current)
> point.getHalfWidth());
}
log.debug(deviceId, "[PassengerFlowStatistics] completeEnter = {}, completeLeave = {}",
historical.getCompleteEnter(),
historical.getCompleteLeave());
if (Objects.nonNull(historical.getLeaveIntersectionLine()) && Boolean.TRUE.equals(
historical.getCompleteLeave())) {
if (Objects.isNull(historical.getEnterIntersectionLine())) {
if (Objects.equals(trajectoryAlgorithm, "multi_segments")) {
// 未知区域进入,从轨迹方向进行确认
List<Line> segment = trajectoryCache.getSegment(getModule(), deviceId,
historical.getConfigId(), point.getObjectId());
if (CollectionUtil.isNotEmpty(segment)) {
historical.setEnterIntersectionLine(
GeometryUtils.findRayIntersection(historical.getCurrentConfig().getPolygon(),
segment.get(0).reverse()));
}
} else {
log.debug(deviceId, "[PassengerFlowStatistics] getCrossAreaWay crossAreaWayEnum = {}.",
crossAreaWayEnum);
reset(historical, point);
return crossAreaWayEnum;
}
}
if (Objects.nonNull(historical.getEnterIntersectionLine())) {
// 完成了离开
List<Point> polygon = historical.getCurrentConfig().getPolygon();
Point outside = polygon.get(polygon.size() - 2);
if (Boolean.TRUE.equals(historical.getCurrentConfig().getEnterFromA2B())) {
outside = polygon.get(0);
}
Point enter = historical.getEnterIntersectionLine().getIntersection();
Point leave = historical.getLeaveIntersectionLine().getIntersection();
Line cuttingLine = historical.getCurrentConfig().getCuttingLine();
CrossAreaWayEnum crossAreaWay = getCrossAreaWay(enter,
leave, cuttingLine,
outside);
log.debug(deviceId,
"[PassengerFlowStatistics] getCrossAreaWay enter = {}, leave = {}, cuttingLine = {}, outside = {}.",
enter, leave, cuttingLine, outside);
reset(historical, point);
return crossAreaWay;
}
}
return CrossAreaWayEnum.USELESS;
}
private void processTrajectoryCache(HistoricalEntity historical, Integer objectId, Point current,
Integer boxHeight) {
Long deviceId = historical.getDeviceId();
String configId = historical.getConfigId();
try {
if (Objects.equals(trajectoryAlgorithm, "multi_segments")) {
trajectoryCache.addPoint(getModule(), deviceId, configId, objectId, current, boxHeight);
} else {
historical.updateInside(current, boxHeight);
}
} catch (Exception e) {
log.error(deviceId,
"trajectory cache add point error, ai module = {}, deviceId = {}, configId = {}, objectId = {}, current = {}.",
getModule(), deviceId, configId, objectId, current, e);
}
}
public boolean reset(HistoricalEntity historical, DevicePointEntity point) {
trajectoryCache.reset(getModule(), historical.getDeviceId(), historical.getConfigId(), point.getObjectId());
return reset(historical);
}
public boolean reset(HistoricalEntity historical) {
historical.setPrevious(null);
historical.setPreviousInside(null);
historical.setEnterIntersectionLine(null);
historical.setCompleteEnter(null);
historical.setLeaveIntersectionLine(null);
historical.setCompleteLeave(null);
return true;
}
public static Integer getPosition(
Map<Integer, DeviceConfigDTO> deviceConfigs,
Point point) {
if (CollectionUtil.isEmpty(deviceConfigs) || point == null) {
return null;
}
for (DeviceConfigDTO deviceConfig : deviceConfigs.values()) {
if (GeometryUtils.isPointInPolygon(deviceConfig.getPolygon(), point)) {
// 区域不重复,如果处于一个区域内,那必然不在另一个区域内
return deviceConfig.getId();
}
}
return null;
}
public static CrossAreaWayEnum getCrossAreaWay(Point enter, Point leave, Line cuttingLine, Point outside) {
boolean fromOutside = GeometryUtils.arePointsOnSameSide(cuttingLine, enter, outside);
boolean toOutside = GeometryUtils.arePointsOnSameSide(cuttingLine, leave, outside);
if (fromOutside) {
if (toOutside) {
return CrossAreaWayEnum.PASS_BY_STORE;
}
return CrossAreaWayEnum.ENTER_STORE;
}
if (toOutside) {
return CrossAreaWayEnum.LEAVE_STORE;
}
return CrossAreaWayEnum.USELESS;
}
}
添加行级注释