From dd86131f5239c33d35612c61fde04d3ece4645f8 Mon Sep 17 00:00:00 2001 From: flonix8 Date: Fri, 8 Dec 2023 18:16:56 +0100 Subject: [PATCH 01/12] Minor refactoring under de.starwit.service.jobs; Preparation for algo impl --- .../src/main/resources/application.properties | 11 ++++---- .../sae/repository/SaeRepository.java | 26 ++++++++++++++++--- .../de/starwit/service/jobs/AbstractJob.java | 14 +++++++--- .../service/jobs/AnalyticsJobCreator.java | 24 +++++++---------- .../service/jobs/AreaOccupancyJob.java | 8 +++--- .../java/de/starwit/service/jobs/JobData.java | 11 +++----- .../starwit/service/jobs/LineCrossingJob.java | 14 ++++++---- 7 files changed, 67 insertions(+), 41 deletions(-) diff --git a/application/src/main/resources/application.properties b/application/src/main/resources/application.properties index da53656..3fdea3e 100644 --- a/application/src/main/resources/application.properties +++ b/application/src/main/resources/application.properties @@ -37,12 +37,13 @@ spring.flyway.placeholder-replacement=false springdoc.swagger-ui.csrf.enabled=true # Configuration -analytics.dataRetrievalRate=1000 +analytics.jobRunInterval=2000 +analytics.maxDataInterval=10000 -sae.datasource.url=jdbc:postgresql://localhost:5435/sae -sae.datasource.username=sae -sae.datasource.password=sae -sae.detection.tablename=detection +sae.datasource.url=jdbc:postgresql://localhost:5432/postgres +sae.datasource.username=postgres +sae.datasource.password=postgres +sae.detection.tablename=data analytics.datasource.username=analytics analytics.datasource.password=analytics diff --git a/persistence/src/main/java/de/starwit/persistence/sae/repository/SaeRepository.java b/persistence/src/main/java/de/starwit/persistence/sae/repository/SaeRepository.java index 26b4eb9..7faefc3 100644 --- a/persistence/src/main/java/de/starwit/persistence/sae/repository/SaeRepository.java +++ b/persistence/src/main/java/de/starwit/persistence/sae/repository/SaeRepository.java @@ -1,12 +1,14 @@ package de.starwit.persistence.sae.repository; import java.time.Instant; +import java.util.Arrays; import java.util.List; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Repository; import de.starwit.persistence.sae.entity.SaeCountEntity; +import de.starwit.persistence.sae.entity.SaeDetectionEntity; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.Query; @@ -20,10 +22,10 @@ public class SaeRepository { @Value("${sae.detection.tablename}") private String hyperTableName; - public List getDetectionData(Instant lastRetrievedTime, String cameraId, + public List getCountData(Instant lastRetrievedTime, String cameraId, Integer detectionClassId) { - String getDetectionData = "" + String queryString = "" + "select capture_ts, class_id, count(object_id)" + " from " + hyperTableName + " where capture_ts > :capturets" @@ -31,7 +33,25 @@ public List getDetectionData(Instant lastRetrievedTime, String c + " and class_id = :classid" + " group by capture_ts, class_id"; - Query q = em.createNativeQuery(getDetectionData, SaeCountEntity.class); + Query q = em.createNativeQuery(queryString, SaeCountEntity.class); + q.setParameter("capturets", lastRetrievedTime); + q.setParameter("cameraid", cameraId); + q.setParameter("classid", detectionClassId); + return q.getResultList(); + } + + public List getDetectionData(Instant lastRetrievedTime, String cameraId, + Integer detectionClassId) { + + String queryString = "" + + "select *" + + " from " + hyperTableName + + " where capture_ts > :capturets" + + " and camera_id = :cameraid" + + " and class_id = :classid" + + " order by capture_ts asc"; + + Query q = em.createNativeQuery(queryString, SaeDetectionEntity.class); q.setParameter("capturets", lastRetrievedTime); q.setParameter("cameraid", cameraId); q.setParameter("classid", detectionClassId); diff --git a/service/src/main/java/de/starwit/service/jobs/AbstractJob.java b/service/src/main/java/de/starwit/service/jobs/AbstractJob.java index 82ea35e..d4f8ffc 100644 --- a/service/src/main/java/de/starwit/service/jobs/AbstractJob.java +++ b/service/src/main/java/de/starwit/service/jobs/AbstractJob.java @@ -1,16 +1,26 @@ package de.starwit.service.jobs; +import java.time.Instant; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import de.starwit.persistence.common.entity.AbstractCaptureEntity; public abstract class AbstractJob { + + @Value("${analytics.maxDataInterval:10000}") + int maxDataInterval; + final Logger log = LoggerFactory.getLogger(this.getClass()); - public void getAndProcessNewData(JobData jobData) throws InterruptedException { + public void run(JobData jobData) throws InterruptedException { + + if (jobData.getLastRetrievedTime().isBefore(Instant.now().minusMillis(maxDataInterval))) { + jobData.setLastRetrievedTime(Instant.now().minusMillis(maxDataInterval)); + } List newData = this.getData(jobData); @@ -32,8 +42,6 @@ public void getAndProcessNewData(JobData jobData) throws InterruptedException } this.process(jobData); - - // Pass data to database output / writer } abstract List getData(JobData jobData); diff --git a/service/src/main/java/de/starwit/service/jobs/AnalyticsJobCreator.java b/service/src/main/java/de/starwit/service/jobs/AnalyticsJobCreator.java index 33ac867..6e5abc0 100644 --- a/service/src/main/java/de/starwit/service/jobs/AnalyticsJobCreator.java +++ b/service/src/main/java/de/starwit/service/jobs/AnalyticsJobCreator.java @@ -6,7 +6,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -18,9 +17,6 @@ public class AnalyticsJobCreator { Logger log = LoggerFactory.getLogger(this.getClass()); - @Value("${analytics.dataRetrievalRate:2000}") - private int dataRetrievalRate; - private List jobsToRun = null; @Autowired @@ -32,7 +28,7 @@ public class AnalyticsJobCreator { @Autowired private LineCrossingJob lineCrossingJob; - @Scheduled(initialDelay = 0, fixedRate = 1000000) + @Scheduled(initialDelay = 0, fixedRate = 10000) private void refreshJobs() { log.info("in refreshJobs"); jobsToRun = new ArrayList<>(); @@ -42,27 +38,27 @@ private void refreshJobs() { } } - @Scheduled(initialDelay = 1000, fixedRate = 10000) + @Scheduled(initialDelay = 1000, fixedRateString = "${analytics.jobRunInterval:10000}") private void runJobs() { log.info("in runJobs"); - try { - if (jobsToRun != null && !jobsToRun.isEmpty()) { - for (JobData job : jobsToRun) { + if (jobsToRun != null && !jobsToRun.isEmpty()) { + for (JobData job : jobsToRun) { + try { + log.debug("Running job: {}", job.getConfig().getName()); switch (job.getConfig().getType()) { case LINE_CROSSING: - lineCrossingJob.getAndProcessNewData(job); + lineCrossingJob.run(job); break; case AREA_OCCUPANCY: - areaOccupancyJob.getAndProcessNewData(job); + areaOccupancyJob.run(job); break; default: break; } - log.debug("Running job: {}", job.getConfig().getName()); + } catch (Exception e) { + log.error("Exception during job run {}", job.getConfig().getName(), e); } } - } catch (Exception e) { - log.error("Exception during running the job", e); } } } diff --git a/service/src/main/java/de/starwit/service/jobs/AreaOccupancyJob.java b/service/src/main/java/de/starwit/service/jobs/AreaOccupancyJob.java index f4c47eb..78c0f43 100644 --- a/service/src/main/java/de/starwit/service/jobs/AreaOccupancyJob.java +++ b/service/src/main/java/de/starwit/service/jobs/AreaOccupancyJob.java @@ -1,7 +1,7 @@ package de.starwit.service.jobs; import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; +import java.util.Queue; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -21,7 +21,7 @@ public class AreaOccupancyJob extends AbstractJob { @Override List getData(JobData jobData) { - return saeRepository.getDetectionData(jobData.getLastRetrievedTime(), + return saeRepository.getCountData(jobData.getLastRetrievedTime(), jobData.getConfig().getCameraId(), jobData.getConfig().getDetectionClassId()); } @@ -29,9 +29,9 @@ List getData(JobData jobData) { @Override void process(JobData jobData) throws InterruptedException { if (jobData != null) { - ArrayBlockingQueue queue = jobData.getInputData(); + Queue queue = jobData.getInputData(); while (queue != null && !queue.isEmpty()) { - areaOccupancyService.addEntry(queue.take()); + areaOccupancyService.addEntry(queue.poll()); } } } diff --git a/service/src/main/java/de/starwit/service/jobs/JobData.java b/service/src/main/java/de/starwit/service/jobs/JobData.java index d9f7a4d..55a5b1b 100644 --- a/service/src/main/java/de/starwit/service/jobs/JobData.java +++ b/service/src/main/java/de/starwit/service/jobs/JobData.java @@ -2,6 +2,7 @@ package de.starwit.service.jobs; import java.time.Instant; +import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import de.starwit.persistence.common.entity.AbstractCaptureEntity; @@ -9,24 +10,20 @@ public class JobData { - private ArrayBlockingQueue inputData; + private Queue inputData; private final AnalyticsJobEntity config; private Instant lastRetrievedTime; JobData(AnalyticsJobEntity config) { - this.inputData = new ArrayBlockingQueue<>(100); + this.inputData = new ArrayBlockingQueue<>(200); this.config = config; this.lastRetrievedTime = Instant.now(); } - public ArrayBlockingQueue getInputData() { + public Queue getInputData() { return inputData; } - public void setInputData(ArrayBlockingQueue inputData) { - this.inputData = inputData; - } - public AnalyticsJobEntity getConfig() { return config; } diff --git a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java index 7c40fe6..b35a7b1 100644 --- a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java +++ b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java @@ -5,24 +5,28 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import de.starwit.persistence.sae.entity.SaeCountEntity; +import de.starwit.persistence.sae.entity.SaeDetectionEntity; import de.starwit.persistence.sae.repository.SaeRepository; @Component -public class LineCrossingJob extends AbstractJob { +public class LineCrossingJob extends AbstractJob { @Autowired private SaeRepository saeRepository; @Override - List getData(JobData jobData) { - return saeRepository.getDetectionData(jobData.getLastRetrievedTime(), jobData.getConfig().getCameraId(), + List getData(JobData jobData) { + return saeRepository.getDetectionData(jobData.getLastRetrievedTime(), + jobData.getConfig().getCameraId(), jobData.getConfig().getDetectionClassId()); } @Override - void process(JobData jobData) { + void process(JobData jobData) { log.info("Processing data"); + jobData.getInputData().forEach(entity -> { + log.info("Encountered detection: {}", entity.getCaptureTs()); + }); } } From 32e5b022795018963edabd0d0b7f1fc0bcb9afd2 Mon Sep 17 00:00:00 2001 From: flonix8 Date: Mon, 11 Dec 2023 19:07:01 +0100 Subject: [PATCH 02/12] Rough implementation for LineCrossingJob almost done (the SQL does not work yet) --- .../de/starwit/service/jobs/AbstractJob.java | 5 +- .../service/jobs/AnalyticsJobCreator.java | 1 - .../java/de/starwit/service/jobs/JobData.java | 2 +- .../starwit/service/jobs/LineCrossingJob.java | 56 +++++++++++++++++-- 4 files changed, 56 insertions(+), 8 deletions(-) diff --git a/service/src/main/java/de/starwit/service/jobs/AbstractJob.java b/service/src/main/java/de/starwit/service/jobs/AbstractJob.java index d4f8ffc..69344b2 100644 --- a/service/src/main/java/de/starwit/service/jobs/AbstractJob.java +++ b/service/src/main/java/de/starwit/service/jobs/AbstractJob.java @@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Value; import de.starwit.persistence.common.entity.AbstractCaptureEntity; +import de.starwit.persistence.sae.entity.SaeDetectionEntity; public abstract class AbstractJob { @@ -30,8 +31,8 @@ public void run(JobData jobData) throws InterruptedException { if (newData != null && !newData.isEmpty()) { jobData.setLastRetrievedTime(newData.get(newData.size() - 1).getCaptureTs()); - for (int i = newData.size() - 1; i >= 0; i--) { - success = jobData.getInputData().offer(newData.get(i)); + for (E dataPoint : newData) { + success = jobData.getInputData().offer(dataPoint); if (!success) { discardCount++; } diff --git a/service/src/main/java/de/starwit/service/jobs/AnalyticsJobCreator.java b/service/src/main/java/de/starwit/service/jobs/AnalyticsJobCreator.java index 6e5abc0..82af603 100644 --- a/service/src/main/java/de/starwit/service/jobs/AnalyticsJobCreator.java +++ b/service/src/main/java/de/starwit/service/jobs/AnalyticsJobCreator.java @@ -40,7 +40,6 @@ private void refreshJobs() { @Scheduled(initialDelay = 1000, fixedRateString = "${analytics.jobRunInterval:10000}") private void runJobs() { - log.info("in runJobs"); if (jobsToRun != null && !jobsToRun.isEmpty()) { for (JobData job : jobsToRun) { try { diff --git a/service/src/main/java/de/starwit/service/jobs/JobData.java b/service/src/main/java/de/starwit/service/jobs/JobData.java index 55a5b1b..45174fd 100644 --- a/service/src/main/java/de/starwit/service/jobs/JobData.java +++ b/service/src/main/java/de/starwit/service/jobs/JobData.java @@ -15,7 +15,7 @@ public class JobData { private Instant lastRetrievedTime; JobData(AnalyticsJobEntity config) { - this.inputData = new ArrayBlockingQueue<>(200); + this.inputData = new ArrayBlockingQueue<>(500); this.config = config; this.lastRetrievedTime = Instant.now(); } diff --git a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java index b35a7b1..47f9e37 100644 --- a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java +++ b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java @@ -1,6 +1,11 @@ package de.starwit.service.jobs; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -14,6 +19,11 @@ public class LineCrossingJob extends AbstractJob { @Autowired private SaeRepository saeRepository; + private static int MAX_WINDOW_SIZE = 3; + + private Line2D COUNTING_LINE = new Line2D.Double(450, 1010, 2240, 1640); + private Map> analyzingWindowByObjId = new HashMap<>(); + @Override List getData(JobData jobData) { return saeRepository.getDetectionData(jobData.getLastRetrievedTime(), @@ -23,10 +33,48 @@ List getData(JobData jobData) { @Override void process(JobData jobData) { - log.info("Processing data"); - jobData.getInputData().forEach(entity -> { - log.info("Encountered detection: {}", entity.getCaptureTs()); - }); + SaeDetectionEntity det; + while ((det = jobData.getInputData().poll()) != null) { + addToAnalyzingWindow(det); + if (isWindowFilled(det)) { + if (objectHasCrossed(det)) { + log.info("{} has crossed line in direction {}", det.getObjectId(), getCrossingDirection(det)); + analyzingWindowByObjId.get(det.getObjectId()).clear(); + } else { + analyzingWindowByObjId.get(det.getObjectId()).removeFirst(); + } + } + } + } + + private void addToAnalyzingWindow(SaeDetectionEntity det) { + if (analyzingWindowByObjId.get(det.getObjectId()) == null) { + LinkedList newWindow = new LinkedList<>(); + analyzingWindowByObjId.put(det.getObjectId(), newWindow); + } + Point2D center = centerFrom(det); + analyzingWindowByObjId.get(det.getObjectId()).addLast(center); + } + + private boolean isWindowFilled(SaeDetectionEntity det) { + return analyzingWindowByObjId.get(det.getObjectId()).size() >= MAX_WINDOW_SIZE; + } + + private boolean objectHasCrossed(SaeDetectionEntity det) { + Point2D firstPoint = analyzingWindowByObjId.get(det.getObjectId()).getFirst(); + Point2D lastPoint = analyzingWindowByObjId.get(det.getObjectId()).getLast(); + Line2D trajectory = new Line2D.Double(firstPoint, lastPoint); + // log.info("Check crossing for {} from {} to {}", det.getObjectId(), firstPoint, lastPoint); + return trajectory.intersectsLine(COUNTING_LINE); + } + + private int getCrossingDirection(SaeDetectionEntity det) { + Point2D trajectoryEnd = analyzingWindowByObjId.get(det.getObjectId()).getLast(); + return COUNTING_LINE.relativeCCW(trajectoryEnd); + } + + private Point2D centerFrom(SaeDetectionEntity det) { + return new Point2D.Double(det.getMaxX() - det.getMinX(), det.getMaxY() - det.getMinY()); } } From 89af6a47975d2afbd535520058ac15dfc11f5ca0 Mon Sep 17 00:00:00 2001 From: flonix8 Date: Tue, 12 Dec 2023 10:25:37 +0100 Subject: [PATCH 03/12] Kind of works; Switch LineCrossingJob to SaeDao --- .../persistence/sae/repository/SaeDao.java | 25 +++++++++---------- .../de/starwit/service/jobs/AbstractJob.java | 1 - .../starwit/service/jobs/LineCrossingJob.java | 13 +++++----- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/persistence/src/main/java/de/starwit/persistence/sae/repository/SaeDao.java b/persistence/src/main/java/de/starwit/persistence/sae/repository/SaeDao.java index d0e0195..bdb8dd5 100644 --- a/persistence/src/main/java/de/starwit/persistence/sae/repository/SaeDao.java +++ b/persistence/src/main/java/de/starwit/persistence/sae/repository/SaeDao.java @@ -11,38 +11,37 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; import de.starwit.persistence.sae.entity.SaeDetectionEntity; import de.starwit.persistence.sae.entity.SaeDetectionRowMapper; +@Component public class SaeDao { - @Value("${sae.detection.tablename}") - private static String hyperTableName; - - private static String getDetectionDataSql = "select * from " - + hyperTableName - + " where \"capture_ts\" > ? " - + "and \"camera_id\" = ? " - + "and \"class_id\" = ?" - + "order by \"capture_ts\" ASC"; + private String getDetectionDataSql; @Autowired @Qualifier("saeJdbcTemplate") private JdbcTemplate saeJdbcTemplate; - @Value("${sae.detectionsTableName}") - private String detectionTableName; - Logger log = LoggerFactory.getLogger(this.getClass()); + public SaeDao(@Value("${sae.detection.tablename}") String hyperTableName) { + this.getDetectionDataSql = "select * from " + + hyperTableName + + " where \"capture_ts\" > ? " + + "and \"camera_id\" = ? " + + "and \"class_id\" = ? " + + "order by \"capture_ts\" ASC"; + } + public List getDetectionData(Instant lastRetrievedTime, String cameraId, Integer detectionClassId) { try { LocalDateTime ldt = LocalDateTime.ofInstant(lastRetrievedTime, java.time.ZoneId.systemDefault()); List result = saeJdbcTemplate.query(getDetectionDataSql, new SaeDetectionRowMapper(), ldt, cameraId, detectionClassId); - log.info("count in getDetectingData{}", result.size()); return result; } catch (Exception e) { log.error("Error in getDetectionData", e); diff --git a/service/src/main/java/de/starwit/service/jobs/AbstractJob.java b/service/src/main/java/de/starwit/service/jobs/AbstractJob.java index 69344b2..ef0ab8f 100644 --- a/service/src/main/java/de/starwit/service/jobs/AbstractJob.java +++ b/service/src/main/java/de/starwit/service/jobs/AbstractJob.java @@ -8,7 +8,6 @@ import org.springframework.beans.factory.annotation.Value; import de.starwit.persistence.common.entity.AbstractCaptureEntity; -import de.starwit.persistence.sae.entity.SaeDetectionEntity; public abstract class AbstractJob { diff --git a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java index 47f9e37..466c38f 100644 --- a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java +++ b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java @@ -1,23 +1,23 @@ package de.starwit.service.jobs; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.awt.geom.Line2D; -import java.awt.geom.Point2D; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import de.starwit.persistence.sae.entity.SaeDetectionEntity; -import de.starwit.persistence.sae.repository.SaeRepository; +import de.starwit.persistence.sae.repository.SaeDao; @Component public class LineCrossingJob extends AbstractJob { @Autowired - private SaeRepository saeRepository; + private SaeDao saeDao; private static int MAX_WINDOW_SIZE = 3; @@ -26,7 +26,7 @@ public class LineCrossingJob extends AbstractJob { @Override List getData(JobData jobData) { - return saeRepository.getDetectionData(jobData.getLastRetrievedTime(), + return saeDao.getDetectionData(jobData.getLastRetrievedTime(), jobData.getConfig().getCameraId(), jobData.getConfig().getDetectionClassId()); } @@ -64,7 +64,6 @@ private boolean objectHasCrossed(SaeDetectionEntity det) { Point2D firstPoint = analyzingWindowByObjId.get(det.getObjectId()).getFirst(); Point2D lastPoint = analyzingWindowByObjId.get(det.getObjectId()).getLast(); Line2D trajectory = new Line2D.Double(firstPoint, lastPoint); - // log.info("Check crossing for {} from {} to {}", det.getObjectId(), firstPoint, lastPoint); return trajectory.intersectsLine(COUNTING_LINE); } @@ -74,7 +73,7 @@ private int getCrossingDirection(SaeDetectionEntity det) { } private Point2D centerFrom(SaeDetectionEntity det) { - return new Point2D.Double(det.getMaxX() - det.getMinX(), det.getMaxY() - det.getMinY()); + return new Point2D.Double(det.getMinX() + (det.getMaxX() - det.getMinX()), det.getMinY() + (det.getMaxY() - det.getMinY())); } } From 1afcb9c8a5d4c399dcbfaf06ee604e930a78e551 Mon Sep 17 00:00:00 2001 From: flonix8 Date: Tue, 12 Dec 2023 11:57:47 +0100 Subject: [PATCH 04/12] Finish first roughly working version of line crossing algorithm --- .../starwit/service/jobs/LineCrossingJob.java | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java index 466c38f..b3e8d9e 100644 --- a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java +++ b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java @@ -2,6 +2,8 @@ import java.awt.geom.Line2D; import java.awt.geom.Point2D; +import java.time.Duration; +import java.time.Instant; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -19,10 +21,10 @@ public class LineCrossingJob extends AbstractJob { @Autowired private SaeDao saeDao; - private static int MAX_WINDOW_SIZE = 3; + private static int TARGET_WINDOW_SIZE_SEC = 1; - private Line2D COUNTING_LINE = new Line2D.Double(450, 1010, 2240, 1640); - private Map> analyzingWindowByObjId = new HashMap<>(); + private Line2D COUNTING_LINE = new Line2D.Double(1180, 1163, 2414, 1614); + private Map> trajectoryByObjId = new HashMap<>(); @Override List getData(JobData jobData) { @@ -35,40 +37,58 @@ List getData(JobData jobData) { void process(JobData jobData) { SaeDetectionEntity det; while ((det = jobData.getInputData().poll()) != null) { - addToAnalyzingWindow(det); - if (isWindowFilled(det)) { + addToTrajectory(det); + trimTrajectory(det); + if (isTrajectoryValid(det)) { if (objectHasCrossed(det)) { log.info("{} has crossed line in direction {}", det.getObjectId(), getCrossingDirection(det)); - analyzingWindowByObjId.get(det.getObjectId()).clear(); + trajectoryByObjId.get(det.getObjectId()).clear(); } else { - analyzingWindowByObjId.get(det.getObjectId()).removeFirst(); + trajectoryByObjId.get(det.getObjectId()).removeFirst(); } } } } - private void addToAnalyzingWindow(SaeDetectionEntity det) { - if (analyzingWindowByObjId.get(det.getObjectId()) == null) { - LinkedList newWindow = new LinkedList<>(); - analyzingWindowByObjId.put(det.getObjectId(), newWindow); + private void addToTrajectory(SaeDetectionEntity det) { + if (trajectoryByObjId.get(det.getObjectId()) == null) { + LinkedList newWindow = new LinkedList<>(); + trajectoryByObjId.put(det.getObjectId(), newWindow); } - Point2D center = centerFrom(det); - analyzingWindowByObjId.get(det.getObjectId()).addLast(center); + trajectoryByObjId.get(det.getObjectId()).addLast(det); } - private boolean isWindowFilled(SaeDetectionEntity det) { - return analyzingWindowByObjId.get(det.getObjectId()).size() >= MAX_WINDOW_SIZE; + private void trimTrajectory(SaeDetectionEntity det) { + Instant trajectoryEnd = trajectoryByObjId.get(det.getObjectId()).getLast().getCaptureTs(); + + boolean trimming = true; + while (trimming) { + Instant trajectoryStart = trajectoryByObjId.get(det.getObjectId()).peekFirst().getCaptureTs(); + if (Duration.between(trajectoryStart, trajectoryEnd).toSeconds() > TARGET_WINDOW_SIZE_SEC) { + trajectoryByObjId.get(det.getObjectId()).removeFirst(); + } else { + trimming = false; + } + } + + } + + private boolean isTrajectoryValid(SaeDetectionEntity det) { + Instant trajectoryStart = trajectoryByObjId.get(det.getObjectId()).getFirst().getCaptureTs(); + Instant trajectoryEnd = trajectoryByObjId.get(det.getObjectId()).getLast().getCaptureTs(); + return Duration.between(trajectoryStart, trajectoryEnd).toSeconds() >= TARGET_WINDOW_SIZE_SEC; } private boolean objectHasCrossed(SaeDetectionEntity det) { - Point2D firstPoint = analyzingWindowByObjId.get(det.getObjectId()).getFirst(); - Point2D lastPoint = analyzingWindowByObjId.get(det.getObjectId()).getLast(); + Point2D firstPoint = centerFrom(trajectoryByObjId.get(det.getObjectId()).getFirst()); + Point2D lastPoint = centerFrom(trajectoryByObjId.get(det.getObjectId()).getLast()); Line2D trajectory = new Line2D.Double(firstPoint, lastPoint); return trajectory.intersectsLine(COUNTING_LINE); } + private int getCrossingDirection(SaeDetectionEntity det) { - Point2D trajectoryEnd = analyzingWindowByObjId.get(det.getObjectId()).getLast(); + Point2D trajectoryEnd = centerFrom(trajectoryByObjId.get(det.getObjectId()).getLast()); return COUNTING_LINE.relativeCCW(trajectoryEnd); } From 894ca489bce0eacbf9d26e1d32bc2a9f139a1804 Mon Sep 17 00:00:00 2001 From: flonix8 Date: Tue, 12 Dec 2023 15:59:21 +0100 Subject: [PATCH 05/12] Add TrajectoryStore to account for multiple jobs --- .../starwit/service/jobs/LineCrossingJob.java | 56 ++++++++++++------- .../starwit/service/jobs/TrajectoryStore.java | 56 +++++++++++++++++++ 2 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 service/src/main/java/de/starwit/service/jobs/TrajectoryStore.java diff --git a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java index b3e8d9e..b5c7e20 100644 --- a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java +++ b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java @@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import de.starwit.persistence.databackend.entity.AnalyticsJobEntity; import de.starwit.persistence.sae.entity.SaeDetectionEntity; import de.starwit.persistence.sae.repository.SaeDao; @@ -23,8 +24,11 @@ public class LineCrossingJob extends AbstractJob { private static int TARGET_WINDOW_SIZE_SEC = 1; - private Line2D COUNTING_LINE = new Line2D.Double(1180, 1163, 2414, 1614); - private Map> trajectoryByObjId = new HashMap<>(); + // private Line2D COUNTING_LINE = new Line2D.Double(1180, 1163, 2414, 1614); + + private Map trajectoryStores = new HashMap<>(); + private TrajectoryStore activeStore; + private Line2D activeCountingLine; @Override List getData(JobData jobData) { @@ -35,37 +39,41 @@ List getData(JobData jobData) { @Override void process(JobData jobData) { + activeCountingLine = new Line2D.Double(1180, 1163, 2414, 1614); + activeStore = getStore(jobData.getConfig()); + SaeDetectionEntity det; while ((det = jobData.getInputData().poll()) != null) { - addToTrajectory(det); + activeStore.addDetection(det); trimTrajectory(det); if (isTrajectoryValid(det)) { if (objectHasCrossed(det)) { log.info("{} has crossed line in direction {}", det.getObjectId(), getCrossingDirection(det)); - trajectoryByObjId.get(det.getObjectId()).clear(); + activeStore.clear(det); } else { - trajectoryByObjId.get(det.getObjectId()).removeFirst(); + activeStore.removeFirst(det); } } } } - private void addToTrajectory(SaeDetectionEntity det) { - if (trajectoryByObjId.get(det.getObjectId()) == null) { - LinkedList newWindow = new LinkedList<>(); - trajectoryByObjId.put(det.getObjectId(), newWindow); + private TrajectoryStore getStore(AnalyticsJobEntity jobConfig) { + if (trajectoryStores.get(jobConfig.getId()) == null) { + TrajectoryStore newStore = new TrajectoryStore(); + trajectoryStores.put(jobConfig.getId(), newStore); } - trajectoryByObjId.get(det.getObjectId()).addLast(det); - } + return trajectoryStores.get(jobConfig.getId()); + } + private void trimTrajectory(SaeDetectionEntity det) { - Instant trajectoryEnd = trajectoryByObjId.get(det.getObjectId()).getLast().getCaptureTs(); + Instant trajectoryEnd = activeStore.getLast(det).getCaptureTs(); boolean trimming = true; while (trimming) { - Instant trajectoryStart = trajectoryByObjId.get(det.getObjectId()).peekFirst().getCaptureTs(); + Instant trajectoryStart = activeStore.getFirst(det).getCaptureTs(); if (Duration.between(trajectoryStart, trajectoryEnd).toSeconds() > TARGET_WINDOW_SIZE_SEC) { - trajectoryByObjId.get(det.getObjectId()).removeFirst(); + activeStore.removeFirst(det);; } else { trimming = false; } @@ -74,26 +82,32 @@ private void trimTrajectory(SaeDetectionEntity det) { } private boolean isTrajectoryValid(SaeDetectionEntity det) { - Instant trajectoryStart = trajectoryByObjId.get(det.getObjectId()).getFirst().getCaptureTs(); - Instant trajectoryEnd = trajectoryByObjId.get(det.getObjectId()).getLast().getCaptureTs(); + Instant trajectoryStart = activeStore.getFirst(det).getCaptureTs(); + Instant trajectoryEnd = activeStore.getLast(det).getCaptureTs(); return Duration.between(trajectoryStart, trajectoryEnd).toSeconds() >= TARGET_WINDOW_SIZE_SEC; } private boolean objectHasCrossed(SaeDetectionEntity det) { - Point2D firstPoint = centerFrom(trajectoryByObjId.get(det.getObjectId()).getFirst()); - Point2D lastPoint = centerFrom(trajectoryByObjId.get(det.getObjectId()).getLast()); + Point2D firstPoint = centerFrom(activeStore.getFirst(det)); + Point2D lastPoint = centerFrom(activeStore.getLast(det)); Line2D trajectory = new Line2D.Double(firstPoint, lastPoint); - return trajectory.intersectsLine(COUNTING_LINE); + return trajectory.intersectsLine(activeCountingLine); } private int getCrossingDirection(SaeDetectionEntity det) { - Point2D trajectoryEnd = centerFrom(trajectoryByObjId.get(det.getObjectId()).getLast()); - return COUNTING_LINE.relativeCCW(trajectoryEnd); + Point2D trajectoryEnd = centerFrom(activeStore.getLast(det)); + return activeCountingLine.relativeCCW(trajectoryEnd); } private Point2D centerFrom(SaeDetectionEntity det) { return new Point2D.Double(det.getMinX() + (det.getMaxX() - det.getMinX()), det.getMinY() + (det.getMaxY() - det.getMinY())); } + private Line2D lineFrom(AnalyticsJobEntity jobConfig) { + Point2D pt1 = new Point2D.Double(jobConfig.getGeometryPoints().get(0).getX(), jobConfig.getGeometryPoints().get(0).getY()); + Point2D pt2 = new Point2D.Double(jobConfig.getGeometryPoints().get(1).getX(), jobConfig.getGeometryPoints().get(1).getY()); + return new Line2D.Double(pt1, pt2); + } + } diff --git a/service/src/main/java/de/starwit/service/jobs/TrajectoryStore.java b/service/src/main/java/de/starwit/service/jobs/TrajectoryStore.java new file mode 100644 index 0000000..4d191bc --- /dev/null +++ b/service/src/main/java/de/starwit/service/jobs/TrajectoryStore.java @@ -0,0 +1,56 @@ +package de.starwit.service.jobs; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import de.starwit.persistence.sae.entity.SaeDetectionEntity; + +public class TrajectoryStore { + private Map> trajectoryByObjId = new HashMap<>(); + + public void addDetection(SaeDetectionEntity det) { + if (trajectoryByObjId.get(det.getObjectId()) == null) { + LinkedList trajectory = new LinkedList<>(); + trajectoryByObjId.put(det.getObjectId(), trajectory); + } + trajectoryByObjId.get(det.getObjectId()).addLast(det); + } + + public SaeDetectionEntity getFirst(SaeDetectionEntity det) { + LinkedList trajectory = trajectoryByObjId.get(det.getObjectId()); + if (trajectory == null || trajectory.isEmpty()) { + return null; + } + return trajectory.getFirst(); + } + + public SaeDetectionEntity getLast(SaeDetectionEntity det) { + LinkedList trajectory = trajectoryByObjId.get(det.getObjectId()); + if (trajectory == null || trajectory.isEmpty()) { + return null; + } + return trajectory.getLast(); + } + + public void removeFirst(SaeDetectionEntity det) { + LinkedList trajectory = trajectoryByObjId.get(det.getObjectId()); + if (trajectory != null) { + trajectory.pollFirst(); + } + } + + public void removeLast(SaeDetectionEntity det) { + LinkedList trajectory = trajectoryByObjId.get(det.getObjectId()); + if (trajectory != null) { + trajectory.pollLast(); + } + } + + public void clear(SaeDetectionEntity det) { + LinkedList trajectory = trajectoryByObjId.get(det.getObjectId()); + if (trajectory != null) { + trajectory.clear(); + } + } +} From 68c5b484126860bfe176d72d2023df74e2f904dc Mon Sep 17 00:00:00 2001 From: flonix8 Date: Tue, 12 Dec 2023 16:30:34 +0100 Subject: [PATCH 06/12] Regularly clean up TrajectoryStore --- .../starwit/service/jobs/LineCrossingJob.java | 4 +++- .../starwit/service/jobs/TrajectoryStore.java | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java index b5c7e20..6bf3d17 100644 --- a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java +++ b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java @@ -39,8 +39,9 @@ List getData(JobData jobData) { @Override void process(JobData jobData) { - activeCountingLine = new Line2D.Double(1180, 1163, 2414, 1614); + activeCountingLine = lineFrom(jobData.getConfig()); activeStore = getStore(jobData.getConfig()); + log.debug("store size: {}", activeStore.size()); SaeDetectionEntity det; while ((det = jobData.getInputData().poll()) != null) { @@ -55,6 +56,7 @@ void process(JobData jobData) { } } } + activeStore.purge(Duration.ofSeconds(5)); } private TrajectoryStore getStore(AnalyticsJobEntity jobConfig) { diff --git a/service/src/main/java/de/starwit/service/jobs/TrajectoryStore.java b/service/src/main/java/de/starwit/service/jobs/TrajectoryStore.java index 4d191bc..b08e5f1 100644 --- a/service/src/main/java/de/starwit/service/jobs/TrajectoryStore.java +++ b/service/src/main/java/de/starwit/service/jobs/TrajectoryStore.java @@ -1,8 +1,13 @@ package de.starwit.service.jobs; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import de.starwit.persistence.sae.entity.SaeDetectionEntity; @@ -53,4 +58,19 @@ public void clear(SaeDetectionEntity det) { trajectory.clear(); } } + + public void purge(Duration maxAge) { + List keysToDelete = new ArrayList<>(); + Instant cutOff = Instant.now().minus(maxAge); + for (Entry> entry: trajectoryByObjId.entrySet()) { + if (entry.getValue().isEmpty() || entry.getValue().getLast().getCaptureTs().isBefore(cutOff)) { + keysToDelete.add(entry.getKey()); + } + } + keysToDelete.forEach(key -> trajectoryByObjId.remove(key)); + } + + public int size() { + return trajectoryByObjId.size(); + } } From 21672c46c1c63e83788229e1ec46f9c19594ab51 Mon Sep 17 00:00:00 2001 From: flonix8 Date: Tue, 12 Dec 2023 16:48:00 +0100 Subject: [PATCH 07/12] Add data store step (into analytics DB) for line crossing job (does not work yet) --- .../repository/LineCrossingRepository.java | 14 +++++++------- .../service/analytics/LineCrossingService.java | 16 ++++++++++++++-- .../de/starwit/service/jobs/LineCrossingJob.java | 16 ++++++++++++++-- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/persistence/src/main/java/de/starwit/persistence/analytics/repository/LineCrossingRepository.java b/persistence/src/main/java/de/starwit/persistence/analytics/repository/LineCrossingRepository.java index 01c14e7..e850d66 100644 --- a/persistence/src/main/java/de/starwit/persistence/analytics/repository/LineCrossingRepository.java +++ b/persistence/src/main/java/de/starwit/persistence/analytics/repository/LineCrossingRepository.java @@ -6,7 +6,6 @@ import org.springframework.transaction.annotation.Transactional; import de.starwit.persistence.analytics.entity.LineCrossingEntity; -import de.starwit.persistence.sae.entity.SaeCountEntity; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -18,14 +17,15 @@ public class LineCrossingRepository { EntityManager em; @Transactional("analyticsTransactionManager") - public void insert(SaeCountEntity entity) { + public void insert(LineCrossingEntity entity) { // TODO - String insertString = "insert into linecrossing(crossingtime, parkingareaid, objectid, objectclassid) values(:crossingtime, :parkingareaid, :objectid, :classId)"; + String insertString = "insert into linecrossing(crossingtime, parkingareaid, objectid, direction, objectclassid) values(:crossingtime, :parkingareaid, :objectid, :direction, :classId)"; em.createNativeQuery(insertString) - .setParameter("crossingtime", entity.getCaptureTs()) - .setParameter("parkingareaid", 1) - .setParameter("objectid", "unknown") + .setParameter("crossingtime", entity.getCrossingTime()) + .setParameter("parkingareaid", entity.getParkingAreaId()) + .setParameter("objectid", entity.getObjectId()) + .setParameter("direction", entity.getDirection()) .setParameter("classId", entity.getObjectClassId()) .executeUpdate(); } @@ -36,4 +36,4 @@ public List findFirst100() { return em.createNativeQuery(queryString).getResultList(); } -} +} \ No newline at end of file diff --git a/service/src/main/java/de/starwit/service/analytics/LineCrossingService.java b/service/src/main/java/de/starwit/service/analytics/LineCrossingService.java index f28c825..9efb25e 100644 --- a/service/src/main/java/de/starwit/service/analytics/LineCrossingService.java +++ b/service/src/main/java/de/starwit/service/analytics/LineCrossingService.java @@ -1,11 +1,16 @@ package de.starwit.service.analytics; +import java.time.ZoneId; +import java.time.ZonedDateTime; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import de.starwit.persistence.analytics.entity.Direction; +import de.starwit.persistence.analytics.entity.LineCrossingEntity; import de.starwit.persistence.analytics.repository.LineCrossingRepository; -import de.starwit.persistence.sae.entity.SaeCountEntity; +import de.starwit.persistence.sae.entity.SaeDetectionEntity; /** * @@ -19,7 +24,14 @@ public class LineCrossingService { private LineCrossingRepository linecrossingRepository; @Transactional - public void addEntry(SaeCountEntity entity) { + public void addEntry(SaeDetectionEntity det, Long parkingAreaId, Direction direction) { + LineCrossingEntity entity = new LineCrossingEntity(); + entity.setCrossingTime(ZonedDateTime.ofInstant(det.getCaptureTs(), ZoneId.systemDefault())); + entity.setDirection(direction); + entity.setObjectClassId(det.getClassId()); + entity.setObjectId(det.getObjectId()); + entity.setParkingAreaId(parkingAreaId); + linecrossingRepository.insert(entity); } } diff --git a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java index 6bf3d17..6f5cbe5 100644 --- a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java +++ b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java @@ -12,9 +12,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import de.starwit.persistence.analytics.entity.Direction; +import de.starwit.persistence.analytics.repository.LineCrossingRepository; import de.starwit.persistence.databackend.entity.AnalyticsJobEntity; import de.starwit.persistence.sae.entity.SaeDetectionEntity; import de.starwit.persistence.sae.repository.SaeDao; +import de.starwit.service.analytics.LineCrossingService; @Component public class LineCrossingJob extends AbstractJob { @@ -22,6 +25,9 @@ public class LineCrossingJob extends AbstractJob { @Autowired private SaeDao saeDao; + @Autowired + private LineCrossingService lineCrossingService; + private static int TARGET_WINDOW_SIZE_SEC = 1; // private Line2D COUNTING_LINE = new Line2D.Double(1180, 1163, 2414, 1614); @@ -50,6 +56,7 @@ void process(JobData jobData) { if (isTrajectoryValid(det)) { if (objectHasCrossed(det)) { log.info("{} has crossed line in direction {}", det.getObjectId(), getCrossingDirection(det)); + lineCrossingService.addEntry(det, 1L, getCrossingDirection(det)); activeStore.clear(det); } else { activeStore.removeFirst(det); @@ -97,9 +104,14 @@ private boolean objectHasCrossed(SaeDetectionEntity det) { } - private int getCrossingDirection(SaeDetectionEntity det) { + private Direction getCrossingDirection(SaeDetectionEntity det) { Point2D trajectoryEnd = centerFrom(activeStore.getLast(det)); - return activeCountingLine.relativeCCW(trajectoryEnd); + int ccw = activeCountingLine.relativeCCW(trajectoryEnd); + if (ccw == -1) { + return Direction.in; + } else { + return Direction.out; + } } private Point2D centerFrom(SaeDetectionEntity det) { From 66985cea322633c2994bb9d3e92a84cb2ea6f20c Mon Sep 17 00:00:00 2001 From: flonix8 Date: Wed, 13 Dec 2023 08:51:18 +0100 Subject: [PATCH 08/12] Fix analytics insert query (Enums do not work) --- .../analytics/repository/LineCrossingRepository.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/persistence/src/main/java/de/starwit/persistence/analytics/repository/LineCrossingRepository.java b/persistence/src/main/java/de/starwit/persistence/analytics/repository/LineCrossingRepository.java index e850d66..30e4970 100644 --- a/persistence/src/main/java/de/starwit/persistence/analytics/repository/LineCrossingRepository.java +++ b/persistence/src/main/java/de/starwit/persistence/analytics/repository/LineCrossingRepository.java @@ -18,14 +18,13 @@ public class LineCrossingRepository { @Transactional("analyticsTransactionManager") public void insert(LineCrossingEntity entity) { - // TODO String insertString = "insert into linecrossing(crossingtime, parkingareaid, objectid, direction, objectclassid) values(:crossingtime, :parkingareaid, :objectid, :direction, :classId)"; em.createNativeQuery(insertString) .setParameter("crossingtime", entity.getCrossingTime()) .setParameter("parkingareaid", entity.getParkingAreaId()) .setParameter("objectid", entity.getObjectId()) - .setParameter("direction", entity.getDirection()) + .setParameter("direction", entity.getDirection().toString()) .setParameter("classId", entity.getObjectClassId()) .executeUpdate(); } From 30e382a03dee6291a2db5a3884e080d189658235 Mon Sep 17 00:00:00 2001 From: flonix8 Date: Wed, 13 Dec 2023 10:53:10 +0100 Subject: [PATCH 09/12] Switch LineCrossingJob to constructor injection --- .../de/starwit/service/jobs/LineCrossingJob.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java index 6f5cbe5..50eb779 100644 --- a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java +++ b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java @@ -5,7 +5,6 @@ import java.time.Duration; import java.time.Instant; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -13,7 +12,6 @@ import org.springframework.stereotype.Component; import de.starwit.persistence.analytics.entity.Direction; -import de.starwit.persistence.analytics.repository.LineCrossingRepository; import de.starwit.persistence.databackend.entity.AnalyticsJobEntity; import de.starwit.persistence.sae.entity.SaeDetectionEntity; import de.starwit.persistence.sae.repository.SaeDao; @@ -22,20 +20,22 @@ @Component public class LineCrossingJob extends AbstractJob { - @Autowired private SaeDao saeDao; - @Autowired private LineCrossingService lineCrossingService; private static int TARGET_WINDOW_SIZE_SEC = 1; - // private Line2D COUNTING_LINE = new Line2D.Double(1180, 1163, 2414, 1614); - private Map trajectoryStores = new HashMap<>(); private TrajectoryStore activeStore; private Line2D activeCountingLine; + @Autowired + public LineCrossingJob(SaeDao saeDao, LineCrossingService lineCrossingService) { + this.saeDao = saeDao; + this.lineCrossingService = lineCrossingService; + } + @Override List getData(JobData jobData) { return saeDao.getDetectionData(jobData.getLastRetrievedTime(), @@ -82,7 +82,7 @@ private void trimTrajectory(SaeDetectionEntity det) { while (trimming) { Instant trajectoryStart = activeStore.getFirst(det).getCaptureTs(); if (Duration.between(trajectoryStart, trajectoryEnd).toSeconds() > TARGET_WINDOW_SIZE_SEC) { - activeStore.removeFirst(det);; + activeStore.removeFirst(det); } else { trimming = false; } From 71b2f43e488a1317af6d8bfcc06b5205f77169e1 Mon Sep 17 00:00:00 2001 From: flonix8 Date: Wed, 13 Dec 2023 11:18:51 +0100 Subject: [PATCH 10/12] Fix bounding box center calculation --- .../src/main/java/de/starwit/service/jobs/LineCrossingJob.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java index 50eb779..9cd2d00 100644 --- a/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java +++ b/service/src/main/java/de/starwit/service/jobs/LineCrossingJob.java @@ -115,7 +115,7 @@ private Direction getCrossingDirection(SaeDetectionEntity det) { } private Point2D centerFrom(SaeDetectionEntity det) { - return new Point2D.Double(det.getMinX() + (det.getMaxX() - det.getMinX()), det.getMinY() + (det.getMaxY() - det.getMinY())); + return new Point2D.Double(det.getMinX() + ((det.getMaxX() - det.getMinX()) / 2), det.getMinY() + ((det.getMaxY() - det.getMinY()) / 2)); } private Line2D lineFrom(AnalyticsJobEntity jobConfig) { From 64921598a4582045d67ca914d1dc97b189e032c4 Mon Sep 17 00:00:00 2001 From: flonix8 Date: Thu, 14 Dec 2023 15:04:46 +0100 Subject: [PATCH 11/12] Add simple test for LineCrossingJob --- .../service/jobs/LineCrossingJobTest.java | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 service/src/test/java/de/starwit/service/jobs/LineCrossingJobTest.java diff --git a/service/src/test/java/de/starwit/service/jobs/LineCrossingJobTest.java b/service/src/test/java/de/starwit/service/jobs/LineCrossingJobTest.java new file mode 100644 index 0000000..ceeebac --- /dev/null +++ b/service/src/test/java/de/starwit/service/jobs/LineCrossingJobTest.java @@ -0,0 +1,118 @@ +package de.starwit.service.jobs; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.awt.geom.Point2D; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import de.starwit.persistence.analytics.entity.Direction; +import de.starwit.persistence.databackend.entity.AnalyticsJobEntity; +import de.starwit.persistence.databackend.entity.JobType; +import de.starwit.persistence.databackend.entity.PointEntity; +import de.starwit.persistence.sae.entity.SaeDetectionEntity; +import de.starwit.persistence.sae.repository.SaeDao; +import de.starwit.service.analytics.LineCrossingService; + +@ExtendWith(MockitoExtension.class) +public class LineCrossingJobTest { + + @Mock + SaeDao saeDaoMock; + + @Mock + LineCrossingService serviceMock; + + @Test + public void testLineCrossing() throws InterruptedException { + + AnalyticsJobEntity entity = prepareJobEntity(); + JobData jobData = new JobData<>(entity); + + // No point on trajectory should be ON the counting line (b/c direction is then ambiguous) + List detections = createLinearTrajectory( + new Point2D.Double(50, 55), new Point2D.Double(50, 155), + 10, Duration.ofMillis(250)); + when(saeDaoMock.getDetectionData(any(), any(), any())).thenReturn(detections); + + LineCrossingJob testee = new LineCrossingJob(saeDaoMock, serviceMock); + + testee.run(jobData); + + ArgumentCaptor directionCaptor = ArgumentCaptor.forClass(Direction.class); + verify(serviceMock, times(1)).addEntry(any(), any(), directionCaptor.capture()); + + assertThat(directionCaptor.getValue()).isEqualTo(Direction.in); + } + + static AnalyticsJobEntity prepareJobEntity() { + PointEntity pointEntity1 = new PointEntity(); + pointEntity1.setX(0.0); + pointEntity1.setY(100.0); + PointEntity pointEntity2 = new PointEntity(); + pointEntity2.setX(100.0); + pointEntity2.setY(100.0); + + AnalyticsJobEntity entity = new AnalyticsJobEntity(); + entity.setCameraId("camId"); + entity.setDetectionClassId(1); + entity.setGeometryPoints(Arrays.asList(pointEntity1, pointEntity2)); + entity.setType(JobType.LINE_CROSSING); + + return entity; + } + + static List createLinearTrajectory(Point2D start, Point2D end, int numSteps, Duration stepInterval) { + List trajectory = new ArrayList<>(); + + Instant startTime = Instant.now().minusSeconds(100); + + trajectory.add(createDetection(startTime, new Point2D.Double(start.getX(), start.getY()))); + double currentX = start.getX(); + double currentY = start.getY(); + + for (int i = 0; i < numSteps; i++) { + currentX += (end.getX() - start.getX()) / numSteps; + currentY += (end.getY() - start.getY()) / numSteps; + trajectory.add(createDetection(startTime.plus(stepInterval.multipliedBy(i+1)), new Point2D.Double(currentX, currentY))); + } + + return trajectory; + } + + static SaeDetectionEntity createDetection(Instant captureTs, Point2D center) { + SaeDetectionEntity det = new SaeDetectionEntity(); + + det.setCaptureTs(captureTs); + det.setMinX(center.getX() - 50); + det.setMaxX(center.getX() + 50); + det.setMinY(center.getY() - 50); + det.setMaxY(center.getY() + 50); + + // defaults (we do not need to vary these) + det.setCameraId("camId"); + det.setClassId(1); + det.setObjectId("objId"); + det.setConfidence(0.5); + + return det; + } + + static Point2D centerFrom(SaeDetectionEntity det) { + return new Point2D.Double(det.getMinX() + ((det.getMaxX() - det.getMinX()) / 2), det.getMinY() + ((det.getMaxY() - det.getMinY()) / 2)); + } +} From ccf6160b44617f71e5639826b5a5fe50a006206e Mon Sep 17 00:00:00 2001 From: flonix8 Date: Thu, 14 Dec 2023 15:54:16 +0100 Subject: [PATCH 12/12] Remove debugging function --- application/src/main/resources/application.properties | 4 +++- .../java/de/starwit/service/jobs/LineCrossingJobTest.java | 4 ---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/application/src/main/resources/application.properties b/application/src/main/resources/application.properties index 8c6adfb..1ee26d4 100644 --- a/application/src/main/resources/application.properties +++ b/application/src/main/resources/application.properties @@ -53,4 +53,6 @@ analytics.datasource.flyway.locations=classpath:db/migration/analytics,classpath # logging.level.org.springframework.security=DEBUG -# logging.level.org.springframework.web=DEBUG \ No newline at end of file +# logging.level.org.springframework.web=DEBUG + +# spring.jpa.show-sql=true \ No newline at end of file diff --git a/service/src/test/java/de/starwit/service/jobs/LineCrossingJobTest.java b/service/src/test/java/de/starwit/service/jobs/LineCrossingJobTest.java index ceeebac..eb6bbbf 100644 --- a/service/src/test/java/de/starwit/service/jobs/LineCrossingJobTest.java +++ b/service/src/test/java/de/starwit/service/jobs/LineCrossingJobTest.java @@ -17,7 +17,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import de.starwit.persistence.analytics.entity.Direction; @@ -112,7 +111,4 @@ static SaeDetectionEntity createDetection(Instant captureTs, Point2D center) { return det; } - static Point2D centerFrom(SaeDetectionEntity det) { - return new Point2D.Double(det.getMinX() + ((det.getMaxX() - det.getMinX()) / 2), det.getMinY() + ((det.getMaxY() - det.getMinY()) / 2)); - } }