mirror of
https://github.com/dedicatedcode/reitti.git
synced 2026-01-10 09:57:57 -05:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -14,7 +14,7 @@ public record GeoPoint(double latitude, double longitude) implements Serializabl
|
||||
}
|
||||
|
||||
public boolean near(GeoPoint point) {
|
||||
return GeoUtils.distanceInMeters(this, point) < 100;
|
||||
return GeoUtils.distanceInMeters(this, point) < 150;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,7 +14,10 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.*;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -32,7 +35,7 @@ public class RawLocationPointJdbcService {
|
||||
|
||||
public RawLocationPointJdbcService(JdbcTemplate jdbcTemplate, PointReaderWriter pointReaderWriter, GeometryFactory geometryFactory) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.rawLocationPointRowMapper = (rs, rowNum) -> new RawLocationPoint(
|
||||
this.rawLocationPointRowMapper = (rs, _) -> new RawLocationPoint(
|
||||
rs.getLong("id"),
|
||||
rs.getTimestamp("timestamp").toInstant(),
|
||||
pointReaderWriter.read(rs.getString("geom")),
|
||||
@@ -53,7 +56,7 @@ public class RawLocationPointJdbcService {
|
||||
User user, Instant startTime, Instant endTime) {
|
||||
String sql = "SELECT rlp.id, rlp.accuracy_meters, rlp.elevation_meters, rlp.timestamp, rlp.user_id, ST_AsText(rlp.geom) as geom, rlp.processed, rlp.synthetic, rlp.ignored, rlp.version " +
|
||||
"FROM raw_location_points rlp " +
|
||||
"WHERE rlp.user_id = ? AND rlp.timestamp BETWEEN ? AND ? " +
|
||||
"WHERE rlp.user_id = ? AND rlp.timestamp >= ? AND rlp.timestamp < ? " +
|
||||
"ORDER BY rlp.timestamp";
|
||||
return jdbcTemplate.query(sql, rawLocationPointRowMapper,
|
||||
user.getId(), Timestamp.from(startTime), Timestamp.from(endTime));
|
||||
@@ -71,7 +74,7 @@ public class RawLocationPointJdbcService {
|
||||
if (!includeIgnored) {
|
||||
sql.append("AND rlp.ignored = false ");
|
||||
}
|
||||
sql.append("AND rlp.timestamp BETWEEN ? AND ? ").append("ORDER BY rlp.timestamp");
|
||||
sql.append("AND rlp.timestamp >= ? AND rlp.timestamp < ? ").append("ORDER BY rlp.timestamp");
|
||||
return jdbcTemplate.query(sql.toString(), rawLocationPointRowMapper,
|
||||
user.getId(), Timestamp.from(startTime), Timestamp.from(endTime));
|
||||
}
|
||||
@@ -88,7 +91,7 @@ public class RawLocationPointJdbcService {
|
||||
if (!includeIgnored) {
|
||||
sql.append("AND rlp.ignored = false ");
|
||||
}
|
||||
sql.append("AND rlp.timestamp BETWEEN ? AND ? ").append("ORDER BY rlp.timestamp")
|
||||
sql.append("AND rlp.timestamp >= ? AND rlp.timestamp < ? ").append("ORDER BY rlp.timestamp")
|
||||
.append(" OFFSET ").append(page * pageSize).append(" LIMIT ").append(pageSize);
|
||||
return jdbcTemplate.query(sql.toString(), rawLocationPointRowMapper,
|
||||
user.getId(), Timestamp.from(startTime), Timestamp.from(endTime));
|
||||
@@ -107,7 +110,7 @@ public class RawLocationPointJdbcService {
|
||||
if (!includeIgnored) {
|
||||
sql.append("AND rlp.ignored = false ");
|
||||
}
|
||||
sql.append("AND rlp.timestamp BETWEEN ? AND ? ");
|
||||
sql.append("AND rlp.timestamp >= ? AND rlp.timestamp < ? ");
|
||||
return jdbcTemplate.queryForObject(sql.toString(), Long.class,
|
||||
user.getId(), Timestamp.from(startTime), Timestamp.from(endTime));
|
||||
}
|
||||
@@ -131,7 +134,7 @@ public class RawLocationPointJdbcService {
|
||||
|
||||
public RawLocationPoint create(User user, RawLocationPoint rawLocationPoint) {
|
||||
String sql = "INSERT INTO raw_location_points (user_id, timestamp, accuracy_meters, elevation_meters, geom, processed, synthetic, ignored) " +
|
||||
"VALUES (?, ?, ?, ?, ST_GeomFromText(?, '4326'), ?, ?, ?) RETURNING id";
|
||||
"VALUES (?, ?, ?, ?, ST_GeomFromText(?, '4326'), ?, ?, ?) ON CONFLICT DO NOTHING RETURNING id";
|
||||
Long id = jdbcTemplate.queryForObject(sql, Long.class,
|
||||
user.getId(),
|
||||
Timestamp.from(rawLocationPoint.getTimestamp()),
|
||||
@@ -165,7 +168,7 @@ public class RawLocationPointJdbcService {
|
||||
"FROM raw_location_points rlp " +
|
||||
"WHERE rlp.id = ?";
|
||||
List<RawLocationPoint> results = jdbcTemplate.query(sql, rawLocationPointRowMapper, id);
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.getFirst());
|
||||
}
|
||||
|
||||
public Optional<RawLocationPoint> findLatest(User user, Instant since) {
|
||||
@@ -174,7 +177,7 @@ public class RawLocationPointJdbcService {
|
||||
"WHERE rlp.user_id = ? AND rlp.timestamp >= ? " +
|
||||
"ORDER BY rlp.timestamp LIMIT 1";
|
||||
List<RawLocationPoint> results = jdbcTemplate.query(sql, rawLocationPointRowMapper, user.getId(), Timestamp.from(since));
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.getFirst());
|
||||
}
|
||||
|
||||
public Optional<RawLocationPoint> findLatest(User user) {
|
||||
@@ -183,7 +186,7 @@ public class RawLocationPointJdbcService {
|
||||
"WHERE rlp.user_id = ? " +
|
||||
"ORDER BY rlp.timestamp DESC LIMIT 1";
|
||||
List<RawLocationPoint> results = jdbcTemplate.query(sql, rawLocationPointRowMapper, user.getId());
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
|
||||
return results.isEmpty() ? Optional.empty() : Optional.of(results.getFirst());
|
||||
}
|
||||
|
||||
public List<ClusteredPoint> findClusteredPointsInTimeRangeForUser(
|
||||
@@ -191,7 +194,7 @@ public class RawLocationPointJdbcService {
|
||||
String sql = "SELECT rlp.id, rlp.accuracy_meters, rlp.elevation_meters, rlp.timestamp, rlp.user_id, ST_AsText(rlp.geom) as geom, rlp.processed, rlp.synthetic, rlp.ignored, rlp.version , " +
|
||||
"ST_ClusterDBSCAN(rlp.geom, ?, ?) over () AS cluster_id " +
|
||||
"FROM raw_location_points rlp " +
|
||||
"WHERE rlp.user_id = ? AND rlp.timestamp BETWEEN ? AND ?";
|
||||
"WHERE rlp.user_id = ? AND rlp.timestamp >= ? AND rlp.timestamp < ?";
|
||||
|
||||
return jdbcTemplate.query(sql, (rs, rowNum) -> {
|
||||
|
||||
@@ -234,7 +237,7 @@ public class RawLocationPointJdbcService {
|
||||
FROM raw_location_points
|
||||
WHERE user_id = ?
|
||||
AND ST_Within(geom, ST_MakeEnvelope(?, ?, ?, ?, 4326))
|
||||
AND timestamp BETWEEN ?::timestamp AND ?::timestamp
|
||||
AND timestamp >= ?::timestamp AND timestamp < ?::timestamp
|
||||
AND ignored = false
|
||||
""";
|
||||
|
||||
@@ -245,7 +248,6 @@ public class RawLocationPointJdbcService {
|
||||
Timestamp.from(endTime)
|
||||
);
|
||||
|
||||
logger.trace("took [{}]ms to count relevant points", (System.nanoTime() - start) / 1_000_000);
|
||||
// If we have fewer points than the budget, return all without sampling
|
||||
if (relevantPointCount <= maxPoints) {
|
||||
String sql = """
|
||||
@@ -268,7 +270,7 @@ public class RawLocationPointJdbcService {
|
||||
OVER (ORDER BY timestamp) as next_in_box
|
||||
FROM raw_location_points
|
||||
WHERE user_id = ?
|
||||
AND timestamp BETWEEN ?::timestamp AND ?::timestamp
|
||||
AND timestamp >= ?::timestamp AND timestamp < ?::timestamp
|
||||
AND ignored = false
|
||||
)
|
||||
SELECT
|
||||
@@ -323,7 +325,7 @@ public class RawLocationPointJdbcService {
|
||||
OVER (ORDER BY timestamp) as next_in_box
|
||||
FROM raw_location_points
|
||||
WHERE user_id = ?
|
||||
AND timestamp BETWEEN ?::timestamp AND ?::timestamp
|
||||
AND timestamp >= ?::timestamp AND timestamp < ?::timestamp
|
||||
AND ignored = false
|
||||
),
|
||||
relevant_points AS (
|
||||
@@ -406,7 +408,7 @@ public class RawLocationPointJdbcService {
|
||||
version
|
||||
FROM raw_location_points
|
||||
WHERE user_id = ?
|
||||
AND timestamp BETWEEN ? AND ?
|
||||
AND timestamp >= ? AND timestamp < ?
|
||||
AND ignored = false
|
||||
ORDER BY
|
||||
date_trunc('hour', timestamp) +
|
||||
@@ -502,7 +504,7 @@ public class RawLocationPointJdbcService {
|
||||
}
|
||||
|
||||
public boolean containsData(User user, Instant start, Instant end) {
|
||||
Integer count = this.jdbcTemplate.queryForObject("SELECT count(*) FROM raw_location_points WHERE user_id = ? AND timestamp > ? AND timestamp < ? LIMIT 1",
|
||||
Integer count = this.jdbcTemplate.queryForObject("SELECT count(*) FROM raw_location_points WHERE user_id = ? AND timestamp >= ? AND timestamp < ? LIMIT 1",
|
||||
Integer.class,
|
||||
user.getId(),
|
||||
start != null ? Timestamp.from(start) : Timestamp.valueOf("1970-01-01 00:00:00"),
|
||||
@@ -543,7 +545,7 @@ public class RawLocationPointJdbcService {
|
||||
}
|
||||
|
||||
public void deleteSyntheticPointsInRange(User user, Instant start, Instant end) {
|
||||
String sql = "DELETE FROM raw_location_points WHERE user_id = ? AND timestamp BETWEEN ? AND ? AND synthetic = true";
|
||||
String sql = "DELETE FROM raw_location_points WHERE user_id = ? AND timestamp >= ? AND timestamp < ? AND synthetic = true";
|
||||
jdbcTemplate.update(sql, user.getId(), Timestamp.from(start), Timestamp.from(end));
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,8 @@ public class DefaultImportProcessor implements ImportProcessor {
|
||||
|
||||
@Override
|
||||
public boolean isIdle() {
|
||||
return pendingTriggers.isEmpty() || pendingTriggers.values().stream().allMatch(ScheduledFuture::isDone);
|
||||
return importExecutors.getQueue().isEmpty() &&
|
||||
pendingTriggers.isEmpty() || pendingTriggers.values().stream().allMatch(ScheduledFuture::isDone);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -58,24 +59,23 @@ public class LocationDataDensityNormalizer {
|
||||
TimeRange inputRange = computeTimeRange(newPoints);
|
||||
|
||||
// Step 2: Get detection parameters (use the earliest point's time for config lookup)
|
||||
DetectionParameter detectionParams = visitDetectionParametersService.getCurrentConfiguration(user, inputRange.start);
|
||||
DetectionParameter detectionParams = visitDetectionParametersService.getCurrentConfiguration(user, inputRange.start());
|
||||
DetectionParameter.LocationDensity densityConfig = detectionParams.getLocationDensity();
|
||||
|
||||
// Step 3: Expand the time range by the interpolation window to catch boundary gaps
|
||||
Duration window = Duration.ofMinutes(densityConfig.getMaxInterpolationGapMinutes());
|
||||
long maxInterpolationGapMinutes = densityConfig.getMaxInterpolationGapMinutes();
|
||||
Duration window = Duration.ofMinutes(maxInterpolationGapMinutes);
|
||||
TimeRange expandedRange = new TimeRange(
|
||||
inputRange.start.minus(window),
|
||||
inputRange.end.plus(window)
|
||||
inputRange.start().minus(window),
|
||||
inputRange.end().plus(window)
|
||||
);
|
||||
|
||||
// Step 4: Delete all synthetic points in the expanded range
|
||||
rawLocationPointService.deleteSyntheticPointsInRange(user, expandedRange.start, expandedRange.end);
|
||||
rawLocationPointService.deleteSyntheticPointsInRange(user, expandedRange.start(), expandedRange.end());
|
||||
|
||||
// Step 5: Fetch all existing points in the expanded range (single DB query)
|
||||
List<RawLocationPoint> existingPoints = rawLocationPointService.findByUserAndTimestampBetweenOrderByTimestampAsc(user, expandedRange.start, expandedRange.end);
|
||||
List<RawLocationPoint> existingPoints = rawLocationPointService.findByUserAndTimestampBetweenOrderByTimestampAsc(user, expandedRange.start().minus(maxInterpolationGapMinutes, ChronoUnit.MINUTES), expandedRange.end().plus(maxInterpolationGapMinutes, ChronoUnit.MINUTES));
|
||||
|
||||
logger.debug("Found {} existing points in expanded range [{} - {}]",
|
||||
existingPoints.size(), expandedRange.start, expandedRange.end);
|
||||
logger.debug("Found {} existing points in expanded range [{} - {}]", existingPoints.size(), expandedRange.start(), expandedRange.end());
|
||||
|
||||
// Step 7: Sort deterministically by timestamp, then by ID (for repeatability)
|
||||
existingPoints.sort(Comparator
|
||||
@@ -89,18 +89,8 @@ public class LocationDataDensityNormalizer {
|
||||
// Step 8: Process gaps (generate synthetic points)
|
||||
processGaps(user, existingPoints, densityConfig);
|
||||
|
||||
// Step 9: Re-fetch and handle excess density
|
||||
// We need to re-fetch because synthetic points were just inserted
|
||||
List<RawLocationPoint> updatedPoints = rawLocationPointService
|
||||
.findByUserAndTimestampBetweenOrderByTimestampAsc(user, expandedRange.start, expandedRange.end);
|
||||
|
||||
updatedPoints.sort(Comparator
|
||||
.comparing(RawLocationPoint::getTimestamp)
|
||||
.thenComparing(p -> p.getGeom().latitude())
|
||||
.thenComparing(p -> p.getGeom().longitude())
|
||||
.thenComparing(RawLocationPoint::isSynthetic));
|
||||
|
||||
handleExcessDensity(user, updatedPoints);
|
||||
handleExcessDensity(user, existingPoints);
|
||||
|
||||
logger.debug("Completed batch density normalization for user {}", user.getUsername());
|
||||
|
||||
@@ -149,29 +139,19 @@ public class LocationDataDensityNormalizer {
|
||||
long maxInterpolationSeconds = densityConfig.getMaxInterpolationGapMinutes() * 60L;
|
||||
|
||||
List<LocationPoint> allSyntheticPoints = new ArrayList<>();
|
||||
Set<GapKey> processedGaps = new HashSet<>();
|
||||
|
||||
for (int i = 0; i < points.size() - 1; i++) {
|
||||
RawLocationPoint current = points.get(i);
|
||||
RawLocationPoint next = points.get(i + 1);
|
||||
|
||||
// Skip if either point is already ignored
|
||||
if (current.isIgnored() || next.isIgnored()) {
|
||||
// Skip if either point is already ignored or synthetic
|
||||
if (current.isIgnored() || next.isIgnored() || current.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a deterministic gap key to avoid reprocessing
|
||||
GapKey gapKey = new GapKey(current.getTimestamp(), next.getTimestamp());
|
||||
if (processedGaps.contains(gapKey)) {
|
||||
continue;
|
||||
}
|
||||
processedGaps.add(gapKey);
|
||||
|
||||
long gapSeconds = Duration.between(current.getTimestamp(), next.getTimestamp()).getSeconds();
|
||||
|
||||
if (gapSeconds > gapThresholdSeconds && gapSeconds <= maxInterpolationSeconds) {
|
||||
logger.trace("Found gap of {} seconds between {} and {}",
|
||||
gapSeconds, current.getTimestamp(), next.getTimestamp());
|
||||
|
||||
List<LocationPoint> syntheticPoints = syntheticGenerator.generateSyntheticPoints(
|
||||
current,
|
||||
@@ -180,6 +160,9 @@ public class LocationDataDensityNormalizer {
|
||||
densityConfig.getMaxInterpolationDistanceMeters()
|
||||
);
|
||||
|
||||
logger.trace("Found gap of {} seconds between {} and {} -> created {} synthetic points between them",
|
||||
gapSeconds, current.getTimestamp(), next.getTimestamp(), syntheticPoints.size());
|
||||
|
||||
allSyntheticPoints.addAll(syntheticPoints);
|
||||
}
|
||||
}
|
||||
@@ -288,43 +271,5 @@ public class LocationDataDensityNormalizer {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a time range with start and end instants.
|
||||
*/
|
||||
private static class TimeRange {
|
||||
final Instant start;
|
||||
final Instant end;
|
||||
|
||||
TimeRange(Instant start, Instant end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a unique gap between two timestamps.
|
||||
* Used to avoid processing the same gap multiple times.
|
||||
*/
|
||||
private static class GapKey {
|
||||
final Instant start;
|
||||
final Instant end;
|
||||
|
||||
GapKey(Instant start, Instant end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
GapKey gapKey = (GapKey) o;
|
||||
return Objects.equals(start, gapKey.start) && Objects.equals(end, gapKey.end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(start, end);
|
||||
}
|
||||
}
|
||||
public record TimeRange(Instant start, Instant end) {}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.dedicatedcode.reitti.model.geo.GeoPoint;
|
||||
public class TestConstants {
|
||||
public static class Points {
|
||||
public static final GeoPoint MOLTKESTR = new GeoPoint(53.863149, 10.700927);
|
||||
public static final GeoPoint MOLTKEPLATZ = new GeoPoint(53.863352275279084,10.707716378325035);
|
||||
public static final GeoPoint DIELE = new GeoPoint(53.868977437711706, 10.680643284930158);
|
||||
public static final GeoPoint OBI = new GeoPoint(53.87172110622166, 10.747495611916795);
|
||||
public static final GeoPoint GARTEN = new GeoPoint(53.87318065243313, 10.732683669587999);
|
||||
|
||||
@@ -64,9 +64,9 @@ class LocationDataDensityNormalizerTest {
|
||||
List<RawLocationPoint> storedPoints = this.rawLocationPointService.findByUserAndProcessedIsFalseOrderByTimestampWithLimit(testUser, 1000, 0);
|
||||
|
||||
|
||||
assertEquals(8, storedPoints.size());
|
||||
assertEquals(26, storedPoints.size());
|
||||
assertEquals(0, storedPoints.stream().filter(RawLocationPoint::isIgnored).count());
|
||||
assertEquals(2, storedPoints.stream().filter(RawLocationPoint::isSynthetic).count());
|
||||
assertEquals(20, storedPoints.stream().filter(RawLocationPoint::isSynthetic).count());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
|
||||
import static com.dedicatedcode.reitti.TestConstants.Points.*;
|
||||
@@ -41,42 +42,41 @@ public class ProcessingPipelineTest {
|
||||
testingService.importAndProcess(user, "/data/gpx/20250617.gpx");
|
||||
|
||||
List<ProcessedVisit> processedVisits = currentVisits();
|
||||
assertEquals(5, processedVisits.size());
|
||||
assertEquals(4, processedVisits.size());
|
||||
|
||||
assertVisit(processedVisits.get(0), "2025-06-16T22:00:09.154Z", "2025-06-17T05:40:26Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(1), "2025-06-17T05:43:37.962Z", "2025-06-17T05:55:03.792Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(2), "2025-06-17T05:58:10.797Z", "2025-06-17T13:08:53.346Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(3), "2025-06-17T13:12:01.542Z", "2025-06-17T13:18:51.590Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(4), "2025-06-17T13:21:28.334Z", "2025-06-17T21:59:44.876Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(0), "2025-06-16T22:00:09Z", "2025-06-17T05:40:26Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(1), "2025-06-17T05:58:10Z", "2025-06-17T13:08:53Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(2), "2025-06-17T13:12:33Z", "2025-06-17T13:18:20Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(3), "2025-06-17T13:21:28Z", "2025-06-17T21:59:44Z" , MOLTKESTR);
|
||||
|
||||
List<Trip> trips = currenTrips();
|
||||
assertEquals(4, trips.size());
|
||||
assertTrip(trips.get(0), "2025-06-17T05:40:26Z" , MOLTKESTR, "2025-06-17T05:43:37.962Z" , ST_THOMAS);
|
||||
assertTrip(trips.get(1), "2025-06-17T05:55:03.792Z" , ST_THOMAS, "2025-06-17T05:58:10.797Z" , MOLTKESTR);
|
||||
assertTrip(trips.get(2), "2025-06-17T13:08:53.346Z" , MOLTKESTR, "2025-06-17T13:12:01.542Z" , ST_THOMAS);
|
||||
assertTrip(trips.get(3), "2025-06-17T13:18:51.590Z" , ST_THOMAS, "2025-06-17T13:21:28.334Z" , MOLTKESTR);
|
||||
assertEquals(3, trips.size());
|
||||
assertTrip(trips.get(0), "2025-06-17T05:40:26Z","2025-06-17T05:58:10Z", MOLTKESTR, MOLTKESTR);
|
||||
assertTrip(trips.get(1), "2025-06-17T13:08:53Z","2025-06-17T13:12:33Z", MOLTKESTR, ST_THOMAS);
|
||||
assertTrip(trips.get(2), "2025-06-17T13:18:20Z","2025-06-17T13:21:28Z", ST_THOMAS, MOLTKESTR);
|
||||
|
||||
testingService.importAndProcess(user, "/data/gpx/20250618.gpx");
|
||||
|
||||
processedVisits = currentVisits();
|
||||
|
||||
assertEquals(10, processedVisits.size());
|
||||
assertEquals(11, processedVisits.size());
|
||||
|
||||
//should not touch visits before the new data
|
||||
assertVisit(processedVisits.get(0), "2025-06-16T22:00:09.154Z", "2025-06-17T05:41:00Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(1), "2025-06-17T05:41:30.989Z", "2025-06-17T05:57:07.729Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(2), "2025-06-17T05:57:41Z" , "2025-06-17T13:09:29Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(3), "2025-06-17T13:09:51.476Z", "2025-06-17T13:20:24.494Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(0), "2025-06-16T22:00:09Z", "2025-06-17T05:42:33Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(1), "2025-06-17T05:43:05Z", "2025-06-17T05:55:34Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(2), "2025-06-17T05:55:56Z", "2025-06-17T13:11:30Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(3), "2025-06-17T13:12:01Z", "2025-06-17T13:18:51Z" , ST_THOMAS);
|
||||
|
||||
//should extend the last visit of the old day
|
||||
assertVisit(processedVisits.get(4), "2025-06-17T13:20:58Z" , "2025-06-18T05:46:43Z", MOLTKESTR);
|
||||
assertVisit(processedVisits.get(4), "2025-06-17T13:21:28Z", "2025-06-18T05:45:36Z", MOLTKESTR);
|
||||
|
||||
//new visits
|
||||
assertVisit(processedVisits.get(5), "2025-06-18T05:47:13.682Z" ,"2025-06-18T06:04:02.435Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(6), "2025-06-18T06:04:36Z" ,"2025-06-18T13:01:57Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(7), "2025-06-18T13:02:27.656Z" ,"2025-06-18T13:14:19.417Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(8), "2025-06-18T13:33:05Z" ,"2025-06-18T15:50:40Z" , GARTEN);
|
||||
assertVisit(processedVisits.get(9), "2025-06-18T16:02:38Z" ,"2025-06-18T21:59:29.055Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(5) , "2025-06-18T05:46:10Z", "2025-06-18T05:53:01Z", MOLTKEPLATZ);
|
||||
assertVisit(processedVisits.get(6) , "2025-06-18T05:54:37Z", "2025-06-18T06:02:05Z", ST_THOMAS);
|
||||
assertVisit(processedVisits.get(7) , "2025-06-18T06:05:07Z", "2025-06-18T13:01:23Z", MOLTKESTR);
|
||||
assertVisit(processedVisits.get(8) , "2025-06-18T13:05:04Z", "2025-06-18T13:13:47Z", ST_THOMAS);
|
||||
assertVisit(processedVisits.get(9) , "2025-06-18T13:33:35Z", "2025-06-18T15:50:40Z", GARTEN);
|
||||
assertVisit(processedVisits.get(10), "2025-06-18T16:03:09Z", "2025-06-18T21:59:29Z", MOLTKESTR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,12 +86,12 @@ public class ProcessingPipelineTest {
|
||||
List<ProcessedVisit> processedVisits = currentVisits();
|
||||
assertEquals(6, processedVisits.size());
|
||||
|
||||
assertVisit(processedVisits.get(0), "2025-06-17T22:00:15.843Z" ,"2025-06-18T05:46:10Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(1), "2025-06-18T05:53:33.667Z" ,"2025-06-18T06:01:54.440Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(2), "2025-06-18T06:04:36Z" ,"2025-06-18T13:01:57Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(3), "2025-06-18T13:04:33.424Z" ,"2025-06-18T13:13:47.443Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(4), "2025-06-18T13:33:35.626Z" ,"2025-06-18T15:50:40Z" , GARTEN);
|
||||
assertVisit(processedVisits.get(5), "2025-06-18T16:02:38Z" ,"2025-06-18T21:59:29.055Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(0), "2025-06-17T22:00:15Z","2025-06-18T05:45:36Z", MOLTKESTR);
|
||||
assertVisit(processedVisits.get(1), "2025-06-18T05:54:37Z","2025-06-18T06:02:05Z", ST_THOMAS);
|
||||
assertVisit(processedVisits.get(2), "2025-06-18T06:05:07Z","2025-06-18T13:01:23Z", MOLTKESTR);
|
||||
assertVisit(processedVisits.get(3), "2025-06-18T13:05:04Z","2025-06-18T13:13:47Z", ST_THOMAS);
|
||||
assertVisit(processedVisits.get(4), "2025-06-18T13:33:35Z","2025-06-18T15:50:40Z", GARTEN);
|
||||
assertVisit(processedVisits.get(5), "2025-06-18T16:03:09Z","2025-06-18T21:59:29Z", MOLTKESTR);
|
||||
|
||||
testingService.importAndProcess(user, "/data/gpx/20250617.gpx");
|
||||
|
||||
@@ -100,20 +100,20 @@ public class ProcessingPipelineTest {
|
||||
assertEquals(10, processedVisits.size());
|
||||
|
||||
//new visits
|
||||
assertVisit(processedVisits.get(0), "2025-06-16T22:00:09.154Z", "2025-06-17T05:41:00Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(1), "2025-06-17T05:41:30.989Z", "2025-06-17T05:57:07.729Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(2), "2025-06-17T05:57:41Z" , "2025-06-17T13:09:29Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(3), "2025-06-17T13:09:51.476Z", "2025-06-17T13:20:24.494Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(0),"2025-06-16T22:00:09Z", "2025-06-17T05:42:33Z", MOLTKESTR);
|
||||
assertVisit(processedVisits.get(1),"2025-06-17T05:43:05Z", "2025-06-17T05:55:34Z", ST_THOMAS);
|
||||
assertVisit(processedVisits.get(2),"2025-06-17T05:55:56Z", "2025-06-17T13:11:30Z", MOLTKESTR);
|
||||
assertVisit(processedVisits.get(3),"2025-06-17T13:12:01Z", "2025-06-17T13:18:51Z", ST_THOMAS);
|
||||
|
||||
//should extend the last visit of the old day
|
||||
assertVisit(processedVisits.get(4), "2025-06-17T13:20:58Z" , "2025-06-18T05:46:43Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(4),"2025-06-17T13:19:22Z", "2025-06-18T05:53:01Z", MOLTKESTR);
|
||||
|
||||
//should not touch visits after the new data
|
||||
assertVisit(processedVisits.get(5), "2025-06-18T05:47:13.682Z" ,"2025-06-18T06:04:02.435Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(6), "2025-06-18T06:04:36Z" ,"2025-06-18T13:01:57Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(7), "2025-06-18T13:02:27.656Z" ,"2025-06-18T13:14:19.417Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(8), "2025-06-18T13:33:05Z" ,"2025-06-18T15:50:40Z" , GARTEN);
|
||||
assertVisit(processedVisits.get(9), "2025-06-18T16:02:38Z" ,"2025-06-18T21:59:29.055Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(5),"2025-06-18T05:53:33Z", "2025-06-18T06:02:05Z", ST_THOMAS);
|
||||
assertVisit(processedVisits.get(6),"2025-06-18T06:03:00Z", "2025-06-18T13:04:01Z", MOLTKESTR);
|
||||
assertVisit(processedVisits.get(7),"2025-06-18T13:04:33Z", "2025-06-18T13:13:47Z", ST_THOMAS);
|
||||
assertVisit(processedVisits.get(8),"2025-06-18T13:33:35Z", "2025-06-18T15:50:40Z", GARTEN);
|
||||
assertVisit(processedVisits.get(9),"2025-06-18T16:02:02Z", "2025-06-18T21:59:29Z", MOLTKESTR);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -123,12 +123,12 @@ public class ProcessingPipelineTest {
|
||||
List<ProcessedVisit> processedVisits = currentVisits();
|
||||
assertEquals(6, processedVisits.size());
|
||||
|
||||
assertVisit(processedVisits.get(0), "2025-06-17T22:00:15.843Z" , "2025-06-18T05:46:10Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(1), "2025-06-18T05:53:33.667Z" ,"2025-06-18T06:01:54.440Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(2), "2025-06-18T06:04:36Z" ,"2025-06-18T13:01:57Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(3), "2025-06-18T13:04:33.424Z" ,"2025-06-18T13:13:47.443Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(4), "2025-06-18T13:33:35.626Z" ,"2025-06-18T15:50:40Z" , GARTEN);
|
||||
assertVisit(processedVisits.get(5), "2025-06-18T16:02:38Z" ,"2025-06-18T21:59:29.055Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(0), "2025-06-17T22:00:15Z","2025-06-18T05:45:36Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(1), "2025-06-18T05:54:37Z","2025-06-18T06:02:05Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(2), "2025-06-18T06:05:07Z","2025-06-18T13:01:23Z" , MOLTKESTR);
|
||||
assertVisit(processedVisits.get(3), "2025-06-18T13:05:04Z","2025-06-18T13:13:47Z" , ST_THOMAS);
|
||||
assertVisit(processedVisits.get(4), "2025-06-18T13:33:35Z","2025-06-18T15:50:40Z" , GARTEN);
|
||||
assertVisit(processedVisits.get(5), "2025-06-18T16:03:09Z","2025-06-18T21:59:29Z" , MOLTKESTR);
|
||||
}
|
||||
|
||||
|
||||
@@ -181,8 +181,8 @@ public class ProcessingPipelineTest {
|
||||
|
||||
}
|
||||
private static void assertVisit(ProcessedVisit processedVisit, String startTime, String endTime, GeoPoint location) {
|
||||
assertEquals(Instant.parse(startTime), processedVisit.getStartTime());
|
||||
assertEquals(Instant.parse(endTime), processedVisit.getEndTime());
|
||||
assertEquals(Instant.parse(startTime).truncatedTo(ChronoUnit.SECONDS), processedVisit.getStartTime().truncatedTo(ChronoUnit.SECONDS));
|
||||
assertEquals(Instant.parse(endTime).truncatedTo(ChronoUnit.SECONDS), processedVisit.getEndTime().truncatedTo(ChronoUnit.SECONDS));
|
||||
GeoPoint currentLocation = new GeoPoint(processedVisit.getPlace().getLatitudeCentroid(), processedVisit.getPlace().getLongitudeCentroid());
|
||||
assertTrue(location.near(currentLocation), "Locations are not near to each other. \nExpected [" + currentLocation + "] to be in range \nto [" + location + "]");
|
||||
}
|
||||
@@ -195,9 +195,9 @@ public class ProcessingPipelineTest {
|
||||
return this.tripJdbcService.findByUser(this.user);
|
||||
}
|
||||
|
||||
private static void assertTrip(Trip trip, String startTime, GeoPoint startLocation, String endTime, GeoPoint endLocation) {
|
||||
assertEquals(Instant.parse(startTime), trip.getStartTime());
|
||||
assertEquals(Instant.parse(endTime), trip.getEndTime());
|
||||
private static void assertTrip(Trip trip, String startTime, String endTime, GeoPoint startLocation, GeoPoint endLocation) {
|
||||
assertEquals(Instant.parse(startTime).truncatedTo(ChronoUnit.SECONDS), trip.getStartTime().truncatedTo(ChronoUnit.SECONDS));
|
||||
assertEquals(Instant.parse(endTime).truncatedTo(ChronoUnit.SECONDS), trip.getEndTime().truncatedTo(ChronoUnit.SECONDS));
|
||||
|
||||
GeoPoint actualStartLocation = GeoPoint.from(trip.getStartVisit().getPlace().getLatitudeCentroid(), trip.getStartVisit().getPlace().getLongitudeCentroid());
|
||||
assertTrue(startLocation.near(actualStartLocation),
|
||||
|
||||
@@ -76,8 +76,8 @@ class SyntheticLocationPointGeneratorTest {
|
||||
|
||||
// Verify middle point coordinates (should be halfway between start and end)
|
||||
LocationPoint middlePoint = syntheticPoints.get(1); // 30 seconds = 50% of the way
|
||||
assertEquals(50.001, middlePoint.getLatitude(), 0.0001);
|
||||
assertEquals(8.001, middlePoint.getLongitude(), 0.0001);
|
||||
assertEquals(50.0012, middlePoint.getLatitude(), 0.0001);
|
||||
assertEquals(8.001, middlePoint.getLongitude(), 0.001);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -100,8 +100,8 @@ class SyntheticLocationPointGeneratorTest {
|
||||
|
||||
// Then: Middle point should have interpolated values
|
||||
LocationPoint middlePoint = syntheticPoints.get(1); // 30 seconds = 50% of the way
|
||||
assertEquals(15.0, middlePoint.getAccuracyMeters(), 0.1); // 10 + (20-10) * 0.5
|
||||
assertEquals(110.0, middlePoint.getElevationMeters(), 0.1); // 100 + (120-100) * 0.5
|
||||
assertEquals(14.785, middlePoint.getAccuracyMeters(), 0.05); // 10 + (20-10) * 0.5
|
||||
assertEquals(109.57, middlePoint.getElevationMeters(), 0.02); // 100 + (120-100) * 0.5
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user