Interpolate sparse horizon CSVs instead of rejecting them
Any CSV with ≥ 2 az_deg,alt_deg rows now works. Points are sorted by azimuth, then linearly interpolated to fill all 360 degrees. A 28-point sparse horizon profile interpolates cleanly; an exact 360-point file is used as-is. Also resets the file input after upload so re-uploading the same file works without picking a different one first. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -359,20 +359,44 @@ export default function Settings() {
|
|||||||
const handleHorizonCSV = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleHorizonCSV = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
// Reset input so the same file can be re-uploaded after a reset
|
||||||
|
e.target.value = '';
|
||||||
const text = await file.text();
|
const text = await file.text();
|
||||||
const lines = text.trim().split('\n').slice(1); // skip header
|
const raw: HorizonPoint[] = [];
|
||||||
const points: HorizonPoint[] = [];
|
for (const line of text.trim().split('\n')) {
|
||||||
for (const line of lines) {
|
|
||||||
const [az, alt] = line.split(',').map(Number);
|
const [az, alt] = line.split(',').map(Number);
|
||||||
if (!isNaN(az) && !isNaN(alt)) {
|
if (!isNaN(az) && !isNaN(alt)) {
|
||||||
points.push({ az_deg: Math.round(az) % 360, alt_deg: Math.max(0, Math.min(90, alt)) });
|
raw.push({ az_deg: ((Math.round(az) % 360) + 360) % 360, alt_deg: Math.max(0, Math.min(90, alt)) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (points.length === 360) {
|
if (raw.length < 2) {
|
||||||
setHorizon.mutate(points);
|
alert('CSV must have at least 2 valid az_deg,alt_deg rows.');
|
||||||
} else {
|
return;
|
||||||
alert(`CSV must have exactly 360 rows (got ${points.length}). Format: az_deg,alt_deg`);
|
|
||||||
}
|
}
|
||||||
|
// Sort by azimuth
|
||||||
|
raw.sort((a, b) => a.az_deg - b.az_deg);
|
||||||
|
|
||||||
|
// If already 360 points, use as-is; otherwise interpolate to fill every degree
|
||||||
|
let full: HorizonPoint[];
|
||||||
|
if (raw.length === 360) {
|
||||||
|
full = raw;
|
||||||
|
} else {
|
||||||
|
// Wrap around: append first point at az+360 to close the circle
|
||||||
|
const pts = [...raw, { az_deg: raw[0].az_deg + 360, alt_deg: raw[0].alt_deg }];
|
||||||
|
full = Array.from({ length: 360 }, (_, deg) => {
|
||||||
|
// Find the two surrounding control points
|
||||||
|
let lo = pts[0], hi = pts[pts.length - 1];
|
||||||
|
for (let i = 0; i < pts.length - 1; i++) {
|
||||||
|
if (pts[i].az_deg <= deg && pts[i + 1].az_deg >= deg) {
|
||||||
|
lo = pts[i]; hi = pts[i + 1]; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const span = hi.az_deg - lo.az_deg;
|
||||||
|
const t = span > 0 ? (deg - lo.az_deg) / span : 0;
|
||||||
|
return { az_deg: deg, alt_deg: Math.max(0, lo.alt_deg + t * (hi.alt_deg - lo.alt_deg)) };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setHorizon.mutate(full);
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetHorizon = () => {
|
const resetHorizon = () => {
|
||||||
@@ -396,7 +420,7 @@ export default function Settings() {
|
|||||||
{horizonData?.points && <HorizonPolarChart points={horizonData.points} />}
|
{horizonData?.points && <HorizonPolarChart points={horizonData.points} />}
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||||
<div style={{ fontSize: 12, color: 'var(--text-mid)', maxWidth: 300 }}>
|
<div style={{ fontSize: 12, color: 'var(--text-mid)', maxWidth: 300 }}>
|
||||||
Upload a CSV file with columns <code>az_deg,alt_deg</code>, one row per degree (360 rows total).
|
Upload a CSV with columns <code>az_deg,alt_deg</code>. Sparse files (any number of points ≥ 2) are linearly interpolated to 360°.
|
||||||
</div>
|
</div>
|
||||||
<label style={{
|
<label style={{
|
||||||
display: 'inline-flex',
|
display: 'inline-flex',
|
||||||
|
|||||||
Reference in New Issue
Block a user