fix: ST_AsText WKT 공백 불일치로 인한 daily merge 전량 필터 수정
PostGIS ST_AsText()는 "LINESTRING M (...)" (공백 포함) 반환하지만
Java 코드는 "LINESTRING M(...)" (공백 없음) 형식만 매칭하여
앱 재시작 후 DB fallback/warmup 데이터가 전량 필터되는 버그 수정.
- DB 읽기 시 WKT 정규화 (CacheWarmup, CacheBasedReader 4곳)
- merge processor regex에 \s* 방어적 처리 (Daily/Hourly)
- countWktPoints indexOf('(') 기반으로 개선
- BatchAdmin: dailyJob 수동 실행 시 timeBucket 파라미터 추가
- DataPipeline: L3 표시를 cachedDays → totalVessels로 변경
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
부모
3250da7347
커밋
1544832899
@ -83,7 +83,7 @@ const en = {
|
||||
'pipeline.aisLatest': 'AIS Latest',
|
||||
'pipeline.processLatest': 'Process Latest',
|
||||
'pipeline.cacheOverview': 'Cache Overview',
|
||||
'pipeline.cachedDays': 'days cached',
|
||||
'pipeline.cachedDays': ' days cached',
|
||||
'pipeline.totalHitRate': 'Total Hit Rate',
|
||||
'pipeline.dailyThroughput': 'Daily Throughput Trend',
|
||||
'pipeline.totalProcessed': 'Total Processed',
|
||||
|
||||
@ -157,8 +157,8 @@ export default function DataPipeline() {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-muted">L3 (Daily)</div>
|
||||
<div className="text-lg font-bold">{cacheDetails.l3_daily?.cachedDays ?? 0}</div>
|
||||
<div className="text-xs text-muted">{t('pipeline.cachedDays')}</div>
|
||||
<div className="text-lg font-bold">{formatNumber(cacheDetails.l3_daily?.totalVessels)}</div>
|
||||
<div className="text-xs text-muted">{cacheDetails.l3_daily?.cachedDays ?? 0}{t('pipeline.cachedDays')}</div>
|
||||
</div>
|
||||
</div>
|
||||
{cache && (
|
||||
|
||||
@ -35,7 +35,7 @@ import java.util.regex.Pattern;
|
||||
public class DailyTrackMergeProcessor
|
||||
implements ItemProcessor<List<VesselTrack>, AbnormalDetectionResult>, StepExecutionListener {
|
||||
|
||||
private static final Pattern WKT_COORDS_PATTERN = Pattern.compile("LINESTRING M\\((.+)\\)");
|
||||
private static final Pattern WKT_COORDS_PATTERN = Pattern.compile("LINESTRING M\\s*\\((.+)\\)");
|
||||
private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private final AbnormalTrackDetector abnormalTrackDetector;
|
||||
@ -217,7 +217,9 @@ public class DailyTrackMergeProcessor
|
||||
private int countWktPoints(String wkt) {
|
||||
if (wkt == null || !wkt.startsWith("LINESTRING M")) return 0;
|
||||
try {
|
||||
String coords = wkt.substring("LINESTRING M(".length(), wkt.length() - 1);
|
||||
int parenIdx = wkt.indexOf('(');
|
||||
if (parenIdx < 0) return 0;
|
||||
String coords = wkt.substring(parenIdx + 1, wkt.length() - 1);
|
||||
return coords.split(",").length;
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
|
||||
@ -35,7 +35,7 @@ import java.util.regex.Pattern;
|
||||
public class HourlyTrackMergeProcessor
|
||||
implements ItemProcessor<List<VesselTrack>, AbnormalDetectionResult>, StepExecutionListener {
|
||||
|
||||
private static final Pattern WKT_COORDS_PATTERN = Pattern.compile("LINESTRING M\\((.+)\\)");
|
||||
private static final Pattern WKT_COORDS_PATTERN = Pattern.compile("LINESTRING M\\s*\\((.+)\\)");
|
||||
private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private final AbnormalTrackDetector abnormalTrackDetector;
|
||||
@ -212,7 +212,9 @@ public class HourlyTrackMergeProcessor
|
||||
private int countWktPoints(String wkt) {
|
||||
if (wkt == null || !wkt.startsWith("LINESTRING M")) return 0;
|
||||
try {
|
||||
String coords = wkt.substring("LINESTRING M(".length(), wkt.length() - 1);
|
||||
int parenIdx = wkt.indexOf('(');
|
||||
if (parenIdx < 0) return 0;
|
||||
String coords = wkt.substring(parenIdx + 1, wkt.length() - 1);
|
||||
return coords.split(",").length;
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
|
||||
@ -146,10 +146,15 @@ public class CacheBasedDailyTrackReader implements ItemReader<List<VesselTrack>>
|
||||
|| (endPos == null && rs.getString("end_position") != null)) {
|
||||
parseFailCount[0]++;
|
||||
}
|
||||
// ST_AsText()는 "LINESTRING M (...)" 공백 포함 반환 → Java 내부 형식으로 정규화
|
||||
String geomWkt = rs.getString("geom_text");
|
||||
if (geomWkt != null) {
|
||||
geomWkt = geomWkt.replace("LINESTRING M (", "LINESTRING M(");
|
||||
}
|
||||
VesselTrack track = VesselTrack.builder()
|
||||
.mmsi(mmsi)
|
||||
.timeBucket(rs.getTimestamp("time_bucket").toLocalDateTime())
|
||||
.trackGeom(rs.getString("geom_text"))
|
||||
.trackGeom(geomWkt)
|
||||
.distanceNm(rs.getBigDecimal("distance_nm"))
|
||||
.avgSpeed(rs.getBigDecimal("avg_speed"))
|
||||
.maxSpeed(rs.getBigDecimal("max_speed"))
|
||||
|
||||
@ -146,10 +146,15 @@ public class CacheBasedHourlyTrackReader implements ItemReader<List<VesselTrack>
|
||||
|| (endPos == null && rs.getString("end_position") != null)) {
|
||||
parseFailCount[0]++;
|
||||
}
|
||||
// ST_AsText()는 "LINESTRING M (...)" 공백 포함 반환 → 정규화
|
||||
String geomWkt = rs.getString("geom_text");
|
||||
if (geomWkt != null) {
|
||||
geomWkt = geomWkt.replace("LINESTRING M (", "LINESTRING M(");
|
||||
}
|
||||
VesselTrack track = VesselTrack.builder()
|
||||
.mmsi(mmsi)
|
||||
.timeBucket(rs.getTimestamp("time_bucket").toLocalDateTime())
|
||||
.trackGeom(rs.getString("geom_text"))
|
||||
.trackGeom(geomWkt)
|
||||
.distanceNm(rs.getBigDecimal("distance_nm"))
|
||||
.avgSpeed(rs.getBigDecimal("avg_speed"))
|
||||
.maxSpeed(rs.getBigDecimal("max_speed"))
|
||||
|
||||
@ -169,10 +169,15 @@ public class CacheWarmupService {
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
// ST_AsText()는 "LINESTRING M (...)" 공백 포함 반환 → Java 내부 형식으로 정규화
|
||||
String geomWkt = rs.getString("track_geom");
|
||||
if (geomWkt != null) {
|
||||
geomWkt = geomWkt.replace("LINESTRING M (", "LINESTRING M(");
|
||||
}
|
||||
VesselTrack track = VesselTrack.builder()
|
||||
.mmsi(rs.getString("mmsi"))
|
||||
.timeBucket(rs.getTimestamp("time_bucket").toLocalDateTime())
|
||||
.trackGeom(rs.getString("track_geom"))
|
||||
.trackGeom(geomWkt)
|
||||
.distanceNm(BigDecimal.valueOf(rs.getDouble("distance_nm")))
|
||||
.avgSpeed(BigDecimal.valueOf(rs.getDouble("avg_speed")))
|
||||
.maxSpeed(BigDecimal.valueOf(rs.getDouble("max_speed")))
|
||||
|
||||
@ -87,8 +87,11 @@ public class BatchAdminController {
|
||||
.addString("endTime", end.withNano(0).toString())
|
||||
.addLong("executionTime", System.currentTimeMillis());
|
||||
|
||||
// vesselTrackAggregationJob의 경우 timeBucket 파라미터 추가
|
||||
if ("vesselTrackAggregationJob".equals(jobName)) {
|
||||
// timeBucket 파라미터 추가 (vesselTrack: 5분 단위, daily: 일 시작)
|
||||
if ("dailyAggregationJob".equals(jobName)) {
|
||||
LocalDateTime timeBucket = start.toLocalDate().atStartOfDay();
|
||||
paramsBuilder.addString("timeBucket", timeBucket.toString());
|
||||
} else {
|
||||
LocalDateTime timeBucket = start.withSecond(0).withNano(0)
|
||||
.minusMinutes(start.getMinute() % 5);
|
||||
paramsBuilder.addString("timeBucket", timeBucket.toString());
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user