Permalinks encode a complete Celestiary view — celestial target, simulation time, camera position, orientation, and field of view — in the URL hash. Any URL in the address bar is a shareable, bookmarkable link that reproduces the exact view.
#<path>@<lat>,<lng>,<alt>;<params>
The @ separates the existing celestial path from the camera/time state. The first component
after @ is the geographic position prefix lat,lng,alt (Google Maps style). The remaining
params are semicolon-delimited key=value pairs. Order of params is not significant;
unknown keys are ignored (forward compatibility).
Hashes without @ are legacy path-only URLs and continue to work unchanged.
#sun/earth@30.2638,-97.7526,3282m;t=9233.1234jd;cq=0,0,0,1;fov=45deg
| Component | Encoding | Example | Meaning |
|---|---|---|---|
lat |
trimmed float (4 dp) | 30.2638 |
Latitude in degrees, −90…+90 |
lng |
trimmed float (4 dp) | -97.7526 |
Longitude in degrees, −180…+180 |
alt |
SI-prefixed meters | 3282m |
Altitude above surface |
| Key | Type | Example | Meaning |
|---|---|---|---|
t |
Measure: days from J2000 | 9233.1234jd |
Simulation time |
cq |
4× dimensionless float | 0,0,0,1 |
Camera quaternion (platform-local) |
fov |
Measure: degrees | 45deg |
Camera field of view |
Lat/lng are defined in the body-fixed frame of the target object. The planet's world quaternion (including axial tilt and current sidereal rotation) maps body-local to world space:
- Y-axis = rotation axis (geographic north pole)
lat = asin(y / r)— angle above equatorial planelng = atan2(x, z)— longitude in equatorial plane
At a given t (simulation time), the orbital position and sidereal rotation are fully
determined, so lat/lng are unambiguous. This applies equally to Mars, the Moon, and other
bodies, since VSOP87 (for major planets) and Keplerian elements (for moons) fully determine
positions at any epoch.
cq is the camera quaternion expressed in platform-local space. The camera platform is a
Three.js Object3D parented to the target's orbitPosition, with its −Z axis pointing toward
the solar system origin (platform.lookAt(origin)). Restoring at the same t places the
platform in the same orientation, so cq reproduces the exact viewing direction.
Values follow the @pablo-mayrgundter/measure.js convention: scalar + unit_abbrev, no spaces.
New units (jd, deg) are accumulated locally in js/permalink.js and will be upstreamed to
measure.js later.
d2000 = JD − 2451545.0 where JD is the Julian Day number.
VSOP87 planetary theory uses T = d2000 / 36525 (Julian centuries from J2000), so this is the natural time axis for the simulator. 4 decimal places ≈ 8-second precision, more than adequate for any shareable view.
Examples: 0jd = noon 1 Jan 2000, 9233.1234jd ≈ March 2025, -36524.0jd = 1 Jan 1900.
| Range (abs) | Suffix | Factor |
|---|---|---|
| 0 | 0 |
— |
| ≥ 10¹² | Tm |
10¹² |
| ≥ 10⁹ | Gm |
10⁹ |
| ≥ 10⁶ | Mm |
10⁶ |
| ≥ 10³ | km |
10³ |
| otherwise | m |
1 |
Altitude is rounded to the nearest metre before encoding. Zero is the bare token 0.
Components formatted with parseFloat(v.toFixed(4)).toString(), trimming trailing zeros.
The identity quaternion encodes as 0,0,0,1. 4 decimal places ≈ 0.01° orientation error,
imperceptible at any zoom level.
parseFloat(fov.toFixed(2)).toString() + deg. Trailing zeros trimmed, so 45.00 → 45deg.
The URL is updated automatically via history.replaceState 1 second after the camera settles
(debounced). replaceState does not fire a hashchange event, so no reload occurs.
Updates are suppressed while a camera tween (Shared.targets.tween !== null) is in progress,
ensuring the permalink always represents a stable, settled view. Updates are also suppressed
for objects without a defined radius (e.g. the galaxy root).
On page load from a permalink URL:
- Simulation time is set to the saved
tbefore any navigation. Animation.animateAtJD(scene, jd)positions all planets at that time without advancing the clock.Scene.goTo()reparents the camera platform to the target'sorbitPositionand orients it toward the origin.latLngAltToLocal(lat, lng, alt, radius, planetWorldQuat, platformWorldQuat)converts the saved geographic position back to platform-local camera position.- The camera is snapped directly to that position (tween cancelled) — no fly-in animation.
- Track/follow state:
track=1orfollow=1params for the 't'/'f' key modes. Better suited to an explicit "share" action than auto-update (state is transient). - E2E screenshot test: Playwright + dev server opens a constructed permalink URL, waits for scene settle, takes a screenshot and compares to a stored reference.
- Upstream
jdanddegto@pablo-mayrgundter/measure.js.