修复gis数据获取流程,去掉轨迹概念

This commit is contained in:
sladro 2026-02-11 14:55:04 +08:00
parent ff9143acc7
commit ee275f3868
2 changed files with 130 additions and 114 deletions

View File

@ -125,7 +125,6 @@ public class DataCollectorService {
// 用于缓存所有活跃的MovingObject的最新状态
private final Map<String, MovingObject> activeMovingObjectsCache = new ConcurrentHashMap<>();
private final Map<String, String> selectedTrackByFlightNo = new ConcurrentHashMap<>();
// 记录每个对象最近一次更新的时间戳用于淘汰不活跃对象防止内存长期增长
private final Map<String, Long> activeMovingObjectsLastUpdatedAtMs = new ConcurrentHashMap<>();
@ -421,38 +420,28 @@ public class DataCollectorService {
* 定时采集航空器数据
* - 不进行数据持久化
*/
private List<Aircraft> deduplicateAircraftByLatestTimestamp(List<Aircraft> aircrafts) {
private List<Aircraft> deduplicateAircraftByLatestFlightPoint(List<Aircraft> aircrafts) {
if (aircrafts == null || aircrafts.isEmpty()) {
return Collections.emptyList();
}
Map<String, Map<String, Aircraft>> latestByFlightAndTrack = new HashMap<>();
Map<String, Aircraft> latestByFlight = new HashMap<>();
for (Aircraft aircraft : aircrafts) {
if (aircraft == null || aircraft.getObjectId() == null || aircraft.getObjectId().isBlank()) {
continue;
}
String flightNo = aircraft.getObjectId();
String trackKey = normalizeTrackKey(aircraft.getTrackNumber());
Map<String, Aircraft> byTrack = latestByFlightAndTrack.computeIfAbsent(flightNo, k -> new HashMap<>());
Aircraft existing = byTrack.get(trackKey);
if (existing == null || isCandidateNewer(existing, aircraft)) {
byTrack.put(trackKey, aircraft);
Aircraft existing = latestByFlight.get(flightNo);
if (existing == null || isCandidateNewerBySourceTimestamp(existing, aircraft)) {
latestByFlight.put(flightNo, aircraft);
}
}
List<Aircraft> selected = new java.util.ArrayList<>(latestByFlightAndTrack.size());
for (Map.Entry<String, Map<String, Aircraft>> entry : latestByFlightAndTrack.entrySet()) {
Aircraft selectedAircraft = selectAircraftTrackCandidate(entry.getKey(), entry.getValue());
if (selectedAircraft != null) {
selected.add(selectedAircraft);
}
}
return selected;
return new java.util.ArrayList<>(latestByFlight.values());
}
private boolean isCandidateNewer(Aircraft existing, Aircraft candidate) {
private boolean isCandidateNewerBySourceTimestamp(Aircraft existing, Aircraft candidate) {
Long existingTs = existing.getSourceTimestampMs();
Long candidateTs = candidate.getSourceTimestampMs();
@ -468,72 +457,6 @@ public class DataCollectorService {
return candidateTs >= existingTs;
}
private Aircraft selectAircraftTrackCandidate(String flightNo, Map<String, Aircraft> byTrack) {
if (byTrack == null || byTrack.isEmpty()) {
return null;
}
if (byTrack.size() == 1) {
Aircraft only = byTrack.values().iterator().next();
selectedTrackByFlightNo.put(flightNo, normalizeTrackKey(only.getTrackNumber()));
return only;
}
String selectedTrack = selectedTrackByFlightNo.get(flightNo);
if (selectedTrack != null && byTrack.containsKey(selectedTrack)) {
return byTrack.get(selectedTrack);
}
MovingObject previous = activeMovingObjectsCache.get(flightNo);
Aircraft best = null;
double bestDistance = Double.MAX_VALUE;
if (previous != null && previous.getCurrentPosition() != null) {
for (Aircraft candidate : byTrack.values()) {
if (candidate == null || candidate.getCurrentPosition() == null) {
continue;
}
double d = calculateDistanceMeters(previous.getCurrentPosition(), candidate.getCurrentPosition());
if (d < bestDistance) {
best = candidate;
bestDistance = d;
}
}
}
if (best == null) {
for (Aircraft candidate : byTrack.values()) {
if (best == null || isCandidateNewer(best, candidate)) {
best = candidate;
}
}
}
if (best != null) {
selectedTrackByFlightNo.put(flightNo, normalizeTrackKey(best.getTrackNumber()));
}
return best;
}
private String normalizeTrackKey(String trackNumber) {
if (trackNumber == null || trackNumber.isBlank()) {
return "_DEFAULT_TRACK_";
}
return trackNumber.trim();
}
private double calculateDistanceMeters(Point from, Point to) {
double lat1 = Math.toRadians(from.getY());
double lon1 = Math.toRadians(from.getX());
double lat2 = Math.toRadians(to.getY());
double lon2 = Math.toRadians(to.getX());
double dLat = lat2 - lat1;
double dLon = lon2 - lon1;
double a = Math.sin(dLat / 2.0) * Math.sin(dLat / 2.0)
+ Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2.0) * Math.sin(dLon / 2.0);
double c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1.0 - a));
return 6371000.0 * c;
}
// Fixed-delay scheduling prevents overlap without dropping cycles.
@Scheduled(fixedDelayString = "${data.collector.interval}")
public void collectAircraftData() {
@ -551,9 +474,9 @@ public class DataCollectorService {
log.info("采集到 {} 条航空器数据,用于实时处理", newAircrafts.size());
List<Aircraft> deduplicatedAircrafts = deduplicateAircraftByLatestTimestamp(newAircrafts);
List<Aircraft> deduplicatedAircrafts = deduplicateAircraftByLatestFlightPoint(newAircrafts);
if (deduplicatedAircrafts.size() != newAircrafts.size()) {
log.info("Aircraft batch deduplicated by latest source time: raw={}, deduped={}",
log.info("Aircraft batch merged by flightNo latest source point: raw={}, merged={}",
newAircrafts.size(), deduplicatedAircrafts.size());
}
@ -1325,7 +1248,6 @@ public class DataCollectorService {
Long last = entry.getValue();
if (last == null || last < cutoff) {
activeMovingObjectsCache.remove(entry.getKey());
selectedTrackByFlightNo.remove(entry.getKey());
return true;
}
return false;

View File

@ -45,6 +45,7 @@ public class WebSocketMessageBroadcaster {
private static final int REACQUIRE_AFTER_REJECTS = 3;
private static final double EARTH_RADIUS_METERS = 6371000.0;
private static final double MIN_DT_SECONDS = 0.05;
private static final int AIRCRAFT_REACQUIRE_CONFIRM_POINTS = 2;
private final MessageCacheService messageCacheService;
private final CollisionWebSocketHandler collisionWebSocketHandler;
@ -117,6 +118,18 @@ public class WebSocketMessageBroadcaster {
@Value("${websocket.position.filter.state-ttl-ms:120000}")
private long positionTrackStateTtlMs;
@Value("${websocket.position.filter.aircraft.reacquire-near-factor:0.6}")
private double aircraftReacquireNearFactor;
@Value("${websocket.position.filter.aircraft.max-step-factor:1.8}")
private double aircraftMaxStepFactor;
@Value("${websocket.position.filter.max-effective-dt-ms:5000}")
private long maxEffectiveDtMs;
@Value("${websocket.position.filter.aircraft.speed-cap-multiplier:1.3}")
private double aircraftSpeedCapMultiplier;
private record CoalescedPositionUpdate(PositionUpdatePayload payload, long timestamp) {}
private static final class PositionTrackState {
@ -128,6 +141,10 @@ public class WebSocketMessageBroadcaster {
private double lastFilteredLatitude;
private double lastFilteredLongitude;
private int rejectedCount;
private boolean aircraftPendingReacquire;
private int aircraftReacquireCount;
private double aircraftPendingLatitude;
private double aircraftPendingLongitude;
}
private enum MotionProfile {
@ -430,8 +447,12 @@ public class WebSocketMessageBroadcaster {
}
}
long dtMs = filterTimestampMs - state.lastPayloadTimestampMs;
double dtSec = dtMs / 1000.0;
long sourceDtMs = filterTimestampMs - state.lastPayloadTimestampMs;
long arrivalDtMs = System.currentTimeMillis() - state.lastAcceptedAtMs;
long boundedArrivalDtMs = clampLong(arrivalDtMs, 0L, Math.max(maxEffectiveDtMs, 50L));
long boundedSourceDtMs = clampLong(sourceDtMs, 0L, Math.max(maxEffectiveDtMs, 50L));
long effectiveDtMs = Math.max(boundedSourceDtMs, boundedArrivalDtMs);
double dtSec = Math.max(effectiveDtMs / 1000.0, MIN_DT_SECONDS);
double rawDistance = calculateDistanceMeters(
state.lastRawLatitude,
state.lastRawLongitude,
@ -441,29 +462,83 @@ public class WebSocketMessageBroadcaster {
double rawSpeedMps = rawDistance / Math.max(dtSec, MIN_DT_SECONDS);
AdaptiveFilterParams adaptive = resolveAdaptiveFilterParams(payload.getObjectType(), rawSpeedMps);
boolean aircraft = isAircraftType(payload.getObjectType());
double maxAllowedJump = resolveMaxAllowedJumpMeters(adaptive.maxSpeedMps(), adaptive.jumpMarginMeter(), dtSec);
double effectiveSpeedCapMps = resolveEffectiveSpeedCapMps(payload, adaptive.maxSpeedMps(), aircraft);
double maxAllowedJump = resolveMaxAllowedJumpMeters(effectiveSpeedCapMps, adaptive.jumpMarginMeter(), dtSec);
boolean isOutlierJump = rawDistance > maxAllowedJump;
if (isOutlierJump && !aircraft && state.rejectedCount < REACQUIRE_AFTER_REJECTS - 1) {
state.rejectedCount++;
logDroppedPosition(
"outlier_jump_rejected",
payload,
rawLatitude,
rawLongitude,
String.format("jumpMeter=%.3f,allowedMeter=%.3f,rejectedCount=%d", rawDistance, maxAllowedJump, state.rejectedCount)
);
log.debug("Drop outlier position update: objectId={}, jumpMeter={}, allowedMeter={}",
payload.getObjectId(), rawDistance, maxAllowedJump);
return null;
if (isOutlierJump) {
if (aircraft) {
double nearThreshold = Math.max(maxAllowedJump * clamp(aircraftReacquireNearFactor, 0.2, 2.0), adaptive.jitterMeter());
if (state.aircraftPendingReacquire) {
double pendingDistance = calculateDistanceMeters(
state.aircraftPendingLatitude,
state.aircraftPendingLongitude,
latitude,
longitude
);
if (pendingDistance <= nearThreshold) {
state.aircraftReacquireCount++;
} else {
state.aircraftReacquireCount = 1;
state.aircraftPendingLatitude = latitude;
state.aircraftPendingLongitude = longitude;
}
} else {
state.aircraftPendingReacquire = true;
state.aircraftReacquireCount = 1;
state.aircraftPendingLatitude = latitude;
state.aircraftPendingLongitude = longitude;
}
if (state.aircraftReacquireCount < AIRCRAFT_REACQUIRE_CONFIRM_POINTS) {
logDroppedPosition(
"aircraft_outlier_hold",
payload,
rawLatitude,
rawLongitude,
String.format("jumpMeter=%.3f,allowedMeter=%.3f,confirm=%d/%d",
rawDistance, maxAllowedJump, state.aircraftReacquireCount, AIRCRAFT_REACQUIRE_CONFIRM_POINTS)
);
filteredLatitude = state.lastFilteredLatitude;
filteredLongitude = state.lastFilteredLongitude;
} else {
state.aircraftPendingReacquire = false;
state.aircraftReacquireCount = 0;
double transitionMaxStep = Math.max(maxAllowedJump * clamp(aircraftMaxStepFactor, 1.0, 4.0), adaptive.jitterMeter() * 2.0);
if (rawDistance > transitionMaxStep) {
double ratio = transitionMaxStep / rawDistance;
filteredLatitude = state.lastFilteredLatitude + ratio * (latitude - state.lastFilteredLatitude);
filteredLongitude = state.lastFilteredLongitude + ratio * (longitude - state.lastFilteredLongitude);
log.warn("[TMP-POS-FIX] reason=aircraft_jump_step_limit objectId={} rawLatitude={} rawLongitude={} jumpMeter={} allowedStepMeter={}",
safeObjectId(payload), rawLatitude, rawLongitude, rawDistance, transitionMaxStep);
} else {
filteredLatitude = latitude;
filteredLongitude = longitude;
}
}
} else if (state.rejectedCount < REACQUIRE_AFTER_REJECTS - 1) {
state.rejectedCount++;
logDroppedPosition(
"outlier_jump_rejected",
payload,
rawLatitude,
rawLongitude,
String.format("jumpMeter=%.3f,allowedMeter=%.3f,rejectedCount=%d", rawDistance, maxAllowedJump, state.rejectedCount)
);
log.debug("Drop outlier position update: objectId={}, jumpMeter={}, allowedMeter={}",
payload.getObjectId(), rawDistance, maxAllowedJump);
return null;
} else {
state.rejectedCount = 0;
filteredLatitude = latitude;
filteredLongitude = longitude;
}
}
if (isOutlierJump) {
state.rejectedCount = 0;
filteredLatitude = latitude;
filteredLongitude = longitude;
} else {
if (!isOutlierJump) {
state.rejectedCount = 0;
state.aircraftPendingReacquire = false;
state.aircraftReacquireCount = 0;
if (aircraft) {
if (rawDistance <= adaptive.jitterMeter()) {
filteredLatitude = state.lastFilteredLatitude;
@ -485,12 +560,6 @@ public class WebSocketMessageBroadcaster {
}
}
if (isOutlierJump && aircraft) {
// Aircraft points should not be dropped to avoid visual path gaps.
filteredLatitude = latitude;
filteredLongitude = longitude;
}
state.lastRawLatitude = latitude;
state.lastRawLongitude = longitude;
state.lastPayloadTimestampMs = filterTimestampMs;
@ -536,6 +605,10 @@ public class WebSocketMessageBroadcaster {
state.lastFilteredLatitude = latitude;
state.lastFilteredLongitude = longitude;
state.rejectedCount = 0;
state.aircraftPendingReacquire = false;
state.aircraftReacquireCount = 0;
state.aircraftPendingLatitude = latitude;
state.aircraftPendingLongitude = longitude;
}
private boolean isValidCoordinate(double latitude, double longitude) {
@ -641,4 +714,25 @@ public class WebSocketMessageBroadcaster {
}
return Math.min(value, max);
}
private long clampLong(long value, long min, long max) {
if (value < min) {
return min;
}
return Math.min(value, max);
}
private double resolveEffectiveSpeedCapMps(PositionUpdatePayload payload, double adaptiveSpeedCapMps, boolean aircraft) {
double base = Math.max(adaptiveSpeedCapMps, 0.0);
if (!aircraft || payload == null || payload.getSpeed() == null) {
return base;
}
double speedKph = payload.getSpeed();
if (Double.isNaN(speedKph) || Double.isInfinite(speedKph) || speedKph <= 0.0) {
return base;
}
double speedMps = speedKph / 3.6;
double multiplier = clamp(aircraftSpeedCapMultiplier, 1.0, 3.0);
return Math.max(base, speedMps * multiplier);
}
}