Public
Edited
Mar 15
Paused
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
function m(d) {
let nextImpact = getNextImpactData(d);
let o = {fetched:d.fetched,...nextImpact,...d.summary,...nextImpact};
return o;
}
return objectHistory.map(m);
}
Insert cell
objectHistory.filter(d=>!d.data)
Insert cell
Insert cell
Insert cell
daysObserved = Number(objectDetails.summary.darc.split(" ")[0])
Insert cell
Insert cell
mutable objectDetails = null;
Insert cell
method = objectDetails.summary.method
Insert cell
objectHistory = rawHistory.results.map(d=>({fetched:d.fetched,...JSON.parse(d.text,)})).concat(oldData).map(canonicalizeDetails)
Insert cell
uniqueHistories.map(d=>d.data[0].ip)
Insert cell
uniqueHistories = d3.reverse(objectHistory).filter((d,i,a)=> {
if (!i) {
return true;
}
const d1 = canonicalizeDetails(a[i-1]);
delete d1.fetched ;
delete d1.summary.pdate;
const d2 = canonicalizeDetails(d);
delete d2.fetched
delete d2.summary.pdate;
return (JSON.stringify(d1) !== JSON.stringify(d2));
})
Insert cell
compareObjects(canonicalizeDetails(uniqueHistories.slice(-1)[0]),canonicalizeDetails(uniqueHistories.slice(-2)[0]))
Insert cell
compareObjects(canonicalizeDetails(objectHistory[0]),canonicalizeDetails(objectHistory[1]))
Insert cell
canonicalizeDetails(uniqueHistories[1])
Insert cell
canonicalizeDetails(uniqueHistories[2])
Insert cell
function compareObjects(o1,o2) {
let d1 = {};
let d2 = {};
Object.keys(o1).forEach(k=>{
if (k in o2) {
if (o1[k] === o2[k]) {
return ;
}
if (typeof(o1[k])== "object" && o1[k] && typeof(o2[k])== "object" && o2[k]) {
let c = compareObjects(o1[k],o2[k]);
if (Object.keys(c.d1).length) {
d1[k] = c.d1;
}
if (Object.keys(c.d2).length) {
d2[k] = c.d2;
}
return;
}
d1[k] = o1[k];
d2[k] = o2[k]
}
else {
d1[k] = o1[k];
}
});
Object.keys(o2).forEach(k=>{
if (!(k in o1)) {
d2[k] = o2[k];
}
});
return {d1,d2};
}

Insert cell
{
while (true) {
yield await checkForChanges() ;
yield Promises.tick(5*1000*60);
}
}

Insert cell
function getNextImpactData(objDetails) {
return d3.sort(objDetails.data,d=>d.date)[0];
}
Insert cell
config =({
apiURL: "https://ssd-api.jpl.nasa.gov/sentry.api",
objDes: "2024%20YR4",
});
Insert cell
async function getObjectDetails(des, trackChanges = false) {
let url = config.apiURL + "?des=" + des;
url = getProxyURL(url, false, trackChanges);
const response = await fetch(url);
let json = await response.json();
return canonicalizeDetails(json);
}
Insert cell
JSON.stringify(null)
Insert cell
async function checkForChanges(){
if (!objectDetails || objectDetailsJSON === "null") {
return {};
}
let newObjectDetails = await getObjectDetails(config.objDes);
let d = canonicalizeDetails(newObjectDetails) ;
delete d.fetched;
let newObjectDetailsJSON = JSON.stringify(d);
let changed = newObjectDetailsJSON !== objectDetailsJSON;
if (changed) {
let oldObjectDetails = objectDetails;
mutable objectDetails = newObjectDetails;
let newObjectDetails2 = getObjectDetails(config.objDes, true);
let d = canonicalizeDetails(newObjectDetails2) ;
delete d.fetched;
let newObjectDetails2JSON = JSON.stringify(d);
let changed2 = newObjectDetailsJSON !== newObjectDetails2JSON;
let changedBack = objectDetailsJSON == newObjectDetails2JSON;
console.log("new objectDetails",{changed, changed2, changedBack, oldObjectDetails, newObjectDetails, newObjectDetails2});
if (changed2) {
mutable objectDetails = newObjectDetails2;
return {changed, ...newObjectDetails2};
}
}
else {
console.log("objectDetails unchanged");
}
return {changed, ...newObjectDetails};
}
Insert cell
rawHistory = getHistory()
Insert cell
{
if (objectHistory && objectHistory.length && !objectDetails) {
mutable objectDetails = objectHistory[0];
}
}
Insert cell
oldData = d3.sort([
{
fetched: 1736105294000,
url: "https://web.archive.org/web/20250105112814/https://ssd-api.jpl.nasa.gov/sentry.api?des=2024%20YR4&www=1",
"summary": {
"h": "23.86",
"fullname": "(2024 YR4)",
"nsat": "0",
"first_obs": "2024-12-26",
"mass": "2.52e+08",
"ps_max": "-1.61",
"v_inf": "13.26",
"energy": "9.019e+00",
"last_obs": "2025-01-04",
"ndop": 0,
"ndel": 0,
"method": "IOBS",
"nobs": 137,
"n_imp": 8,
"cdate": "2025-01-04 11:30:30",
"des": "2024 YR4",
"ps_cum": "-1.48",
"diameter": "0.057",
"darc": "8.9206 days",
"pdate": "2025-01-04 23:55:08",
"v_imp": "17.32",
"ip": "0.001359994838",
"ts_max": "1"
},
"signature": {
"version": "2.0",
"source": "NASA/JPL Sentry Data API"
},
"data": [
{
"ts": "0",
"ps": "-6.64",
"sigma_vi": "0.7151",
"energy": "8.775e+00",
"ip": "2.796e-08",
"date": "2047-12-22.05"
},
{
"ts": "0",
"sigma_vi": "0.6821",
"ps": "-5.95",
"ip": "8.765e-08",
"date": "2039-12-23.35",
"energy": "9.022e+00"
},
{
"ts": "0",
"ps": "-7.47",
"sigma_vi": "0.7318",
"energy": "8.993e+00",
"date": "2043-12-23.18",
"ip": "3.302e-09"
},
{
"sigma_vi": "0.6830",
"ps": "-8.07",
"energy": "8.889e+00",
"ip": "2.457e-09",
"date": "2079-12-22.73",
"ts": "0"
},
{
"ts": "0",
"energy": "8.791e+00",
"date": "2047-12-22.07",
"ip": "7.569e-09",
"sigma_vi": "0.7463",
"ps": "-7.20"
},
{
"ip": "1.659e-07",
"date": "2047-12-22.05",
"energy": "8.778e+00",
"ps": "-5.86",
"sigma_vi": "0.6998",
"ts": "0"
},
{
"energy": "9.036e+00",
"date": "2032-12-22.62",
"ip": "3.377e-04",
"ps": "-2.09",
"sigma_vi": "0.7295",
"ts": "0"
},
{
"ts": "1",
"energy": "9.014e+00",
"ip": "1.022e-03",
"date": "2032-12-22.59",
"sigma_vi": "0.6629",
"ps": "-1.61"
}
]
},
{fetched: 1737566113000, url:"https://web.archive.org/web/20250122091513/https://ssd-api.jpl.nasa.gov/sentry.api?des=2024%20YR4&www=1","signature":{"version":"2.0","source":"NASA/JPL Sentry Data API"},"data":[{"ts":"0","ip":"3.410e-09","ps":"-7.84","energy":"8.282e+00","sigma_vi":"0.0713","date":"2067-06-22.73"},{"ts":"0","ip":"2.806e-09","ps":"-7.97","energy":"8.296e+00","sigma_vi":"3.0660","date":"2071-06-22.72"},{"ts":"0","ip":"1.464e-07","ps":"-5.95","energy":"8.088e+00","sigma_vi":"0.1432","date":"2047-12-22.05"},{"ts":"0","ip":"8.672e-07","energy":"8.091e+00","ps":"-5.17","sigma_vi":"0.0810","date":"2047-12-22.05"},{"ip":"4.567e-07","ps":"-5.26","energy":"8.315e+00","ts":"0","date":"2039-12-23.35","sigma_vi":"0.0164"},{"ip":"5.213e-03","ps":"-0.93","energy":"8.308e+00","ts":"1","date":"2032-12-22.59","sigma_vi":"0.0702"}],"summary":{"cdate":"2025-01-21 07:27:44","mass":"2.33e+08","h":"23.92","n_imp":6,"nsat":"0","ndel":0,"v_imp":"17.32","ndop":0,"first_obs":"2024-12-25","v_inf":"13.26","ps_cum":"-0.93","method":"IOBS","ts_max":"1","des":"2024 YR4","diameter":"0.056","fullname":"(2024 YR4)","pdate":"2025-01-21 15:50:50","nobs":206,"darc":"26.836 days","last_obs":"2025-01-21","ps_max":"-0.93","ip":"0.005214476516","energy":"8.308e+00"}}
],
d=>-d.fetched)
Insert cell
async function getHistory(des = config.objDes) {
objectDetails;
let url = config.apiURL + "?des=" + des;
console.log("getting history of "+url);
let r = await getURLhistory(url);
return r;
}
Insert cell
function isObject(obj) {
return typeof obj === 'object' && obj !== null;
}

Insert cell
function canonicalizeDetails(o) {
return {
data: d3.sort(o.data,d=>d.date, d=>d.ps).map(deepCopyWithSort),
signature:deepCopyWithSort(o.signature),
summary: deepCopyWithSort(o.summary),
fetched: o.fetched,
}
}
Insert cell
objectHistory[0]
Insert cell
function deepCopyWithSort(i) {
if (! isObject(i)) {
return i;
}
if (Array.isArray(i)) {
return i.map(deepCopyWithSort);
}
let o = {};
d3.sort(Object.keys(i)).forEach(k=>{
o[k] = deepCopyWithSort(i[k]);
});
return o;
}
Insert cell
canonicalizeDetails(objectDetails)
Insert cell
objectDetailsJSON = {
let d = canonicalizeDetails(objectDetails) ;
delete d.fetched;
return JSON.stringify(d);
}
Insert cell
Insert cell
viewof sigmaRange = Inputs.range([0.005, 2], {label: "Sigma", step: 0.005, value:1})
Insert cell
viewof xRange = Inputs.range([-1, 0], {label: "Distance from mean", step: 0.01, value:-1})
Insert cell
viewof radiusOfImpact = Inputs.range([0.001, 0.2], {label: "Radius for impact", step: 0.001, value:0.01})
Insert cell
Insert cell
daysUntilImpact = 8*365.25 - 4;
Insert cell
randDataSigma = 1;
Insert cell
function pathProb(samples,sigma,line,) {
let p = 1;
samples.forEach((sample,i)=>{
p = updatePathProb(p,sample,sigma,line);
});
return p;
}
Insert cell
function updatePathProb(p,sample,sigma,line,) {
const lineY = line.intercept + line.slope * sample.x;
const pi = normDistUnscaled(sample.y-lineY,0,sigma);
return p * pi;
}
Insert cell
function updatePathProbs(sample,sigma, a=pathProbs){
let finalProbs = {};
let totalP = 0;
a.forEach(d=>{
d.p = updatePathProb(d.p,sample,sigma,d.line);
finalProbs[d.line.finalY] = (finalProbs[d.line.finalY] || 0) + d.p;
totalP += d.p;
});
let probData = Object.entries(finalProbs).map(kv=>{
let y = Number(kv[0]);
let p = kv[1]/totalP;
return {
x: sample.x,
y,
p
};
});
return probData;
}
Insert cell
allFinalProbs = calcAllFinalProbs()
Insert cell
function calcAllFinalProbs(samples=randData, sigma=1, pathProbs=null) {
if (!pathProbs) {
pathProbs = initPathProbs();
}
//return pathProbs;
let a = [];
samples.forEach(sample=>{
// TODO: Find peak probability and position of it
a.push(updatePathProbs(sample,sigma,pathProbs));
});
return a;
}
Insert cell
positionDist = d3.randomNormal(0,randDataSigma);
Insert cell
randData = {
let data = d3.range(0,daysUntilImpact).map(x=>{
let y = positionDist();
return {
x,
y,
};
});
return data;
}
Insert cell
function changeDay(day) {
randData[day].y = positionDist();
return randData[day].y;
}
Insert cell
dayChanged = changeDay(daysRange-1);
Insert cell
viewof daysRange = Inputs.range([2, daysUntilImpact], {label: "Days of observation", step: 1, value: Math.floor(daysObserved)})
Insert cell
{
return "disabled";
dayChanged;

let plotData = finalProbs;

let p0 = plotData.find(d=>d.y==0).p;
let p2 = plotData.find(d=>d.y==2).p;
let pm2 = plotData.find(d=>d.y==-2).p;
//return plotData;
return Plot.plot({
title: "Predicted probability of final position with " + daysRange + " days of observation.",
subtitle: html`P(-2): ${Math.round(pm2*100000)/1000}%<br>P(0): ${Math.round(p0*100000)/1000}%<br>P(2): ${Math.round(p2*100000)/1000}%`,
marginLeft: 55,
marginRight: 55,
width,
x: {
grid: true,
label: "Position"
},
y: {
label: "Probability",
grid: true,
percent: true,
},
marks: [
Plot.dot(plotData, {x: d=>d.y, y: d=>d.p, tip:true}),
]
});
}
Insert cell
peakFinalProb = finalProbs[d3.maxIndex(finalProbs,d=>d.p)]
Insert cell
finalProbs = calcFinalProbs(randData,daysRange);

Insert cell
function calcFinalProbs(data, days) {
let regData = data.slice(0,days);
let y0max = 4/Math.sqrt(days);
//let yFinalMax = 3*y0max * daysUntilImpact / days ;
//let yFinalMax = 2*Math.sqrt(daysUntilImpact);
let yFinalMax = 100;
let steps = 100;
let totalP = 0;

let r = {};
for (let y0 = -y0max ; y0 <= y0max ; y0 += y0max/steps*2) {
for (let y1 = -yFinalMax ; y1 <= yFinalMax ; y1 += yFinalMax/steps*2) {
let slope = (y1 - y0) / daysUntilImpact ;
let p = pathProb(regData,randDataSigma,{intercept:y0,slope, finalY:y1});
totalP += steps*p;
r[y1] = (r[y1] || 0) + steps*p;
}
}
let probData = Object.entries(r).map(kv=>{
return {
y: Number(kv[0]),
p: kv[1]/totalP
};
});
return probData;
}
Insert cell
pathProbs = initPathProbs()
Insert cell
Insert cell
{
return "disabled";
let data = randData ;
let days = daysRange;

let regData = data.slice(0,days);
let dataLM = new ML.SimpleLinearRegression(regData.map(x), regData.map(y));
console.log(dataLM);
let dataCorr = dataLM.score(regData.map(x), regData.map(y));
console.log(dataCorr);
regData.push({
x: daysUntilImpact,
y: dataLM.intercept + dataLM.slope * daysUntilImpact
});
let regLine = [
{x: 0, y:dataLM.intercept},
{x: daysUntilImpact, y:dataLM.intercept + dataLM.slope * daysUntilImpact},
]
//return data;
data.forEach((d,i,a)=>{
});
function y(d) {
return d.y;
}
function x(d) {
return d.x;
}
//return data;
return Plot.plot({
title: "Position at day",
marginLeft: 55,
marginRight: 55,
width,
x: {
grid: true,
label: "Day"
},
y: {
label: "Observed position offset from actual",
grid: true,
percent: false,
},
marks: [
Plot.dot(data, {x, y, opacity: d=>d.x < days ? 1: 0.1 , tip:true}),
Plot.linearRegressionY(data.slice(0,days), {x, y}),
Plot.line(regLine, {x, y, tip:false}),
]
});

}
Insert cell
function normDist(x,mean=0,sigma=1) {
const x1 = (x - mean)/sigma;
const a = 1 / Math.sqrt(2*Math.PI)/sigma;
return Math.exp(-x1*x1/2) * a ;
}
Insert cell
function normDistUnscaled(x,mean=0,sigma=1) {
const x1 = (x - mean)/sigma;
return Math.exp(-x1*x1/2) ;
}
Insert cell
ML = require("https://www.lactame.com/lib/ml/6.0.0/ml.min.js")
Insert cell
constants = ({
au: 149597870700, // meters https://en.wikipedia.org/wiki/Astronomical_unit
ld: 385000000, // https://en.wikipedia.org/wiki/Lunar_distance
})
Insert cell
constants.au / constants.ld
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more