refactor: shadowfinder + exif — symmetric metric, inlined sun math, WMM declination#4
Conversation
…nomics shadowfinder: - Symmetric angular error |atan(h/s) − sunAlt| as the likelihood metric; exported band thresholds (ULTRA_TIGHT/TIGHT/MAIN/VISIBLE_BAND_RAD). - Inline sun-position math: time-only terms precomputed once, lng trig cached per column, integer-indexed loops, preallocated array. ~2.5× speedup over SunCalc-per-cell (74.8ms → 29.7ms with constraint). - estimateBestLocation: split bimodal lat-clusters at the largest gap, use circular mean for longitude (correct across the antimeridian), and 68th-percentile great-circle distance as the accuracy radius. - mainBandCoordinates gains lngWraps to flag antimeridian-crossing bands. - Optional azimuth constraint on generateShadowFinderGrid skips Math.asin for cells outside tolerance; always skip Math.atan2 on night cells. - Hoist magic numbers (KM_PER_DEG_LAT, ACCURACY_PERCENTILE, DEFAULT_AZIMUTH_TOLERANCE_DEG, etc.) to named constants. exif: - WMM2025 magnetic-bearing correction when EXIF reports a magnetic bearing alongside GPS coords (new geomagnetism dep, Apache-2.0). Stores the applied declination as magneticDeclination. - localTime: Date → localWallClock: string (ISO 8601 without Z) to fix the wall-clock-encoded-as-UTC-Date footgun. applyUtcOffset rewritten to accept the string; formatDateInput/formatTimeInput now accept Date | string. - Preserve fractional GPS seconds as milliseconds. - computeFovDeg honors portrait orientation (24mm short sensor dim). - Gate dev console.log behind import.meta.env.DEV (verified stripped from the prod bundle). - More forgiving UTC offset parsing (+HH:MM, +HHMM, +HH); -0 normalized to +0. Tests grow 51 → 80, including SunCalc parity at six sample points, antimeridian centroid handling, WMM declination samples, and constraint-aware grid behavior. CLAUDE.md updated to reflect the new metric and inlined sun math. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
This PR is a follow-up refactor that hardens correctness, accuracy, and performance of the shadow-analysis and EXIF pipelines. It replaces the asymmetric likelihood metric with a symmetric angular error in radians, inlines and caches sun-position math for ~2.5× speedup, makes location estimation robust across the antimeridian and bimodal posteriors, applies WMM2025 declination to magnetic bearings when GPS is available, and replaces the localTime: Date footgun with a wall-clock ISO string. Several magic numbers were also hoisted into named constants and FOV calculation now honors portrait orientation.
Changes:
- shadowfinder.ts: symmetric angular-error likelihood, inlined sun math with column-cached lng trig, antimeridian-safe circular-mean centroid with bimodal lat-cluster splitting, and an optional generation-time azimuth constraint (production callsite intentionally omits it to preserve visualization fallback).
- exif.ts:
localTime: Date→localWallClock: string; magnetic bearings corrected via bundledgeomagnetism(WMM2025) when GPS is present;parseUtcOffsetaccepts+HH:MM,+HHMM,+HH; portrait-awarecomputeFovDeg; dev log gated byimport.meta.env.DEV; fractional GPS seconds preserved. - Constants/visualization:
VISIBLE_BAND_RAD,NIGHT_LIKELIHOOD,DEFAULT_AZIMUTH_TOLERANCE_DEGexported and consumed byShadowFinderVisualizationandAnalysisPanel; CLAUDE.md updated; 29 new tests including SunCalc parity, antimeridian centroid, bimodal clustering, and WMM declination.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/shadowfinder.ts | New symmetric angular-error metric, inlined sun math, antimeridian/bimodal-safe estimateBestLocation, optional azimuth constraint, exported band/tolerance constants. |
| src/lib/shadowfinder.test.ts | Updated tests for new metric, added SunCalc parity, dominantLatCluster, circular-mean longitude, constraint-aware grid, and lngWraps coverage. |
| src/lib/exif.ts | Replaced localTime with localWallClock, added parseUtcOffset/correctMagneticBearing, portrait-aware FOV, dev-gated logging, fractional GPS seconds. |
| src/lib/exif.test.ts | New tests for parseUtcOffset, correctMagneticBearing, portrait FOV, string-overloaded formatters, day-boundary applyUtcOffset. |
| src/components/ShadowFinderVisualization.tsx | Uses exported VISIBLE_BAND_RAD/NIGHT_LIKELIHOOD and LIKELIHOOD_CUTOFF for intersection normalization. |
| src/components/AnalysisPanel.tsx | Migrated to localWallClock and DEFAULT_AZIMUTH_TOLERANCE_DEG. |
| package.json / package-lock.json | Adds geomagnetism@^0.2.0 runtime dependency; lockfile cleanup. |
| CLAUDE.md | Documents inlined sun math, symmetric error metric, bimodal/antimeridian handling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Follow-up to #3 addressing the deeper correctness, accuracy, and perf issues surfaced during code review. Each bullet maps to one item from the review.
src/lib/shadowfinder.tssrc/lib/exif.tssrc/lib/shadowfinder.ts+src/lib/exif.ts— named constants (#10)Hoisted magic numbers: `KM_PER_DEG_LAT`, `ACCURACY_PERCENTILE`, `DEFAULT_AZIMUTH_TOLERANCE_DEG`, `SENSOR_LONG_DIM_MM`/`SENSOR_SHORT_DIM_MM`, `DEFAULT_FOV_DEG`. `AnalysisPanel` now imports the tolerance constant rather than inlining `10`.
Test plan
🤖 Co-authored with Claude Opus 4.7
Made with Cursor