Skip to content

feat: Extrapolate aircraft positions based on last known location, heading and groundspeed#1466

Open
max-zinn wants to merge 2 commits into
VATSIM-Radar:nextfrom
max-zinn:feature/fluid-aircraft-motion
Open

feat: Extrapolate aircraft positions based on last known location, heading and groundspeed#1466
max-zinn wants to merge 2 commits into
VATSIM-Radar:nextfrom
max-zinn:feature/fluid-aircraft-motion

Conversation

@max-zinn
Copy link
Copy Markdown

  • Extending the mandatoryData of VatsimPilots by groundspeed and last_updated timestamp
  • Implement function on render to alter the pilots coordinates based on extrapolation function
  • Increase render refresh rate

🔗 Your VATSIM ID

1432278

🔗 Linked Issue

❓ Type of change

  • 🐞 Bug fix
  • 👌 Enhancement (improve something existing)
  • ✨ New functionality
  • 🧹 Technical change (refactoring, updates and other improvements)

📚 Description

I like how flightradar24 and planefinder seem to extrapolate aircraft positions for a smoother movement on the map. It's very well done there, although it's not perfect (sometimes you can spot the aircraft "jumping" when new data comes in). However, I thought it's a very cool UX feature and thought this could be a cool addition to vatsim-radar 2.

My implementation is by far not perfect, but it's a start and I wanted to get early input on this, before I spend time on something where you say you don't want it at all.

Open issues in this PR

  • Aircrafts wobble when they get updated, this needs some smoothing
  • There's room for improvement to reduce "aircraft jumps" on data updates
    1. Perhaps the most regular cause of aicraft jumps is when they're turning. To solve this, storie the last data of an aircraft and continue with the same turn rate that it had between the last two updates
    2. Also speed changes can be extrapolated to further reduce jumping aircrafts
  • The current implementation may not be really performant since I just span up the refreshrate from 1fps to 20

As general question: I'm not sure how to handle ground traffic. Currently (especially at intersections), traffic just taxi over grass until they snap into their updated position. That's extremely ugly and I'm thinking of restricting the fluid motion to airborne traffic. It's almost impossible to extrapolate ground movement as headings and speeds change quite rapidly and I believe we hit a technical limitation that's splitting realworld from Vatsim ^^

- Extending the mandatoryData of VatsimPilots by groundspeed and last_updated timestamp
- Implement function on render to alter the pilots coordinates based on extrapolation function
- Increase Rate of renderings
Copilot AI review requested due to automatic review settings March 12, 2026 20:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the “mandatory” VATSIM data payload to include each pilot’s groundspeed and last_updated, and uses that data client-side to extrapolate aircraft positions for smoother map motion. It also changes the aircraft layer update loop to render continuously.

Changes:

  • Extend VatsimMandatoryData pilot tuple + converted pilot type to include groundspeed and last_updated.
  • Populate the new fields on the server and propagate them into the client’s mandatory data store.
  • Extrapolate aircraft coordinates during rendering and switch map aircraft updates to a RAF-driven loop.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
app/utils/server/vatsim/update.ts Adds groundspeed and last_updated into the server-built mandatory pilots tuple.
app/types/data/vatsim.ts Updates mandatory tuple/converted types to include the new pilot fields.
app/composables/render/storage.ts Destructures and stores groundspeed/last_updated when setting mandatory data.
app/composables/render/aircraft/index.ts Extrapolates aircraft coordinates using heading/speed/last update time.
app/components/map/layers/MapAircraftList.vue Replaces throttled/watch-driven aircraft rendering with a useRafFn continuous render loop.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

const coordinates = (isSelfFlight && dataStore.vatsim.selfCoordinate.value)
? dataStore.vatsim.selfCoordinate.value.coordinate
: [aircraft.longitude, aircraft.latitude];
? extrapolateCoordinates(dataStore.vatsim.selfCoordinate.value.coordinate, aircraft.heading, aircraft.groundspeed, aircraft.last_updated)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the self-flight branch, the coordinate source is selfCoordinate.value.coordinate (local websocket updates), but the extrapolation timestamp comes from aircraft.last_updated (VATSIM feed). If these clocks/updates are out of sync, the delta time can be wrong (often much larger), causing the self aircraft to be extrapolated far away from its actual position. Consider either not extrapolating when selfCoordinate is present, or using selfCoordinate.value.date as the reference time for extrapolation (and only using VATSIM last_updated for non-self aircraft).

Suggested change
? extrapolateCoordinates(dataStore.vatsim.selfCoordinate.value.coordinate, aircraft.heading, aircraft.groundspeed, aircraft.last_updated)
? extrapolateCoordinates(
dataStore.vatsim.selfCoordinate.value.coordinate,
aircraft.heading,
aircraft.groundspeed,
dataStore.vatsim.selfCoordinate.value.date,
)

Copilot uses AI. Check for mistakes.
Comment on lines +217 to +228
const currentTime = Date.now();
const deltaTimeHours = (currentTime - lastUpdateTime) / (1000 * 60 * 60);

const distanceNauticalMiles = groundspeed * deltaTimeHours;
const distanceDegrees = distanceNauticalMiles / 60;

const headingRadians = degreesToRadians(heading);

const [lon, lat] = coordinates;

const deltaLat = distanceDegrees * Math.cos(headingRadians);
const deltaLon = distanceDegrees * Math.sin(headingRadians) / Math.cos(degreesToRadians(lat));
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extrapolateCoordinates doesn’t guard against invalid/edge inputs: new Date(last_updated).getTime() can be NaN (bad/empty timestamp), deltaTimeHours can be negative (clock skew / future timestamps) or very large (stale data), and Math.cos(degreesToRadians(lat)) can approach 0 near the poles, producing Infinity/NaN longitudes. This can propagate NaN coordinates into OpenLayers and break rendering. Suggest validating the parsed timestamp, clamping deltaTimeHours to a reasonable range (and to >= 0), and protecting against near-zero longitude scaling (or skipping extrapolation when |lat| is too high).

Suggested change
const currentTime = Date.now();
const deltaTimeHours = (currentTime - lastUpdateTime) / (1000 * 60 * 60);
const distanceNauticalMiles = groundspeed * deltaTimeHours;
const distanceDegrees = distanceNauticalMiles / 60;
const headingRadians = degreesToRadians(heading);
const [lon, lat] = coordinates;
const deltaLat = distanceDegrees * Math.cos(headingRadians);
const deltaLon = distanceDegrees * Math.sin(headingRadians) / Math.cos(degreesToRadians(lat));
// If the timestamp is invalid, avoid extrapolation.
if (!Number.isFinite(lastUpdateTime)) {
return coordinates;
}
const currentTime = Date.now();
let deltaTimeHours = (currentTime - lastUpdateTime) / (1000 * 60 * 60);
// Guard against negative, zero, or non-finite time differences.
if (!Number.isFinite(deltaTimeHours) || deltaTimeHours <= 0) {
return coordinates;
}
// Clamp extrapolation horizon to avoid huge jumps for stale data.
const MAX_EXTRAPOLATION_HOURS = 2;
if (deltaTimeHours > MAX_EXTRAPOLATION_HOURS) {
deltaTimeHours = MAX_EXTRAPOLATION_HOURS;
}
// Guard against non-finite groundspeed/heading.
if (!Number.isFinite(groundspeed) || !Number.isFinite(heading)) {
return coordinates;
}
const distanceNauticalMiles = groundspeed * deltaTimeHours;
const distanceDegrees = distanceNauticalMiles / 60;
const headingRadians = degreesToRadians(heading);
const [lon, lat] = coordinates;
const latRadians = degreesToRadians(lat);
const cosLat = Math.cos(latRadians);
// Avoid division by values close to zero near the poles.
const MIN_COS_LAT = 1e-6;
if (!Number.isFinite(cosLat) || Math.abs(cosLat) < MIN_COS_LAT) {
return coordinates;
}
const deltaLat = distanceDegrees * Math.cos(headingRadians);
const deltaLon = distanceDegrees * Math.sin(headingRadians) / cosLat;

Copilot uses AI. Check for mistakes.
Comment on lines 309 to +324
@@ -320,15 +321,13 @@ const debouncedUpdate = useThrottleFn(() => {
tracks: showTracks.value,
});
}
}, 1000, true);
});
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching from a throttled/watch-driven update to an unconditional useRafFn causes setMapAircraft to run every animation frame even when nothing changes. setMapAircraft iterates all shown pilots and mutates OpenLayers features, so this is very likely to become a major CPU/battery regression on large traffic sets. If the goal is smoother motion for extrapolation, consider updating at a capped FPS / fixed interval (e.g., 5–10 Hz), only updating geometry coordinates (not full feature props/tracks) on RAF, or keeping the throttled update and adding a lightweight RAF-only position update path.

Copilot uses AI. Check for mistakes.
@daniluk4000
Copy link
Copy Markdown
Collaborator

lol what is this copilot ai review

I'll take a look on this PR later

@max-zinn
Copy link
Copy Markdown
Author

max-zinn commented Mar 12, 2026

lol what is this copilot ai review

I'll take a look on this PR later

@daniluk4000 Apologies if that was annoying you! 😆 I didn't know it was active for public repositories, too. I have it active automatically for my personal stuff and work stuff. Didn't realize it would automatically review my contributions.

@daniluk4000
Copy link
Copy Markdown
Collaborator

Do you still want to do it? I have fully reworked render, would appreciate if you could update your PR

Also it should be limited to global aircraft limit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants