Skip to content

Sightline Tool (previously ShadeTool)#993

Open
tariqksoliman wants to merge 982 commits into
NASA-AMMOS:developmentfrom
JPL-Devin:development
Open

Sightline Tool (previously ShadeTool)#993
tariqksoliman wants to merge 982 commits into
NASA-AMMOS:developmentfrom
JPL-Devin:development

Conversation

@tariqksoliman

Copy link
Copy Markdown
Member

devin-ai-integration Bot and others added 30 commits May 29, 2026 02:23
Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- Icons: sm (22px) -> md (28px), mdi-14px -> mdi-18px
- Playbar row centered instead of space-between
- Frame label moved to its own row beneath the timeline slider
- Gap between buttons: 2px -> 4px

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- Source section expanded by default, Display and Results collapsed
- Chevron icon rotates 90deg when open, smooth transition
- Generate button always visible outside collapsible sections
- Uses existing Collapsible design-system component

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Replaced diagonal candy-stripe pattern with a diffuse left-to-right
rolling gradient (18% white, ease-in-out, 1.5s loop).

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
… left

- Top card (index 0) now renders on top (highest z-index)
- Accent color drop indicator line shows where card will be placed
- Shademaps tab cards are now drag-reorderable (with elementOrder in store)
- Drag handles standardized to far left of headers in both tabs
- New reorderShadeLayers function for shademaps tab layer z-ordering

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- Add child.stdin error handler to prevent unhandled error crash
- Add height to required field validation (was missing)
- Return 500 JSON error on non-zero Python exit instead of empty body

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
When ShadeTool.shade() hit an error path (getbands null, API failure,
ll2aerll error), it set regenerating=false but never cleared changed.
The auto-generate useEffect saw changed=true + regenerating=false and
immediately re-triggered shade(), creating an infinite loop of getbands
requests flashing between 0% and Generate.

Added lastError flag to break the cycle:
- Error paths set lastError=true, stopping auto-generate retries
- Changing any setting clears lastError, re-enabling auto-generate
- Manual Generate click still works regardless of lastError
- Pan/time changes also clear lastError for fresh attempts

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Remove the separate Sweep tab. Each shade item now has a Mode selector
(Static / Composite / Playback) before the Generate button:
- Static: single-time shade map (existing behavior)
- Composite: time-range heatmap with color ramp, opacity, legend
- Playback: animated timeline with sky dome and indicators

Results section moved below the Generate button, rendering per-mode.

Sweep Start Time, End Time and Step Size merged into the shared vstTime
section (visible when any element uses composite or playback mode).

Playback controls (play/pause/step/timeline) and global sweep options
(Mode, Range) appear at the bottom, visible only when sweep data exists.

New switchElementMode() toggles map layers per-element. CardLegend
extracted to its own component for reuse. Auto-generate disabled for
non-static modes (sweeps are expensive, require explicit click).

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Play/pause/step buttons, timeline slider, and frame label now appear
inside each shade item's Results section when in playback mode.
Removed the global playback controls from ShadePanel bottom.
Frame labels are per-element (vstSweepFrameLabel_{id}).

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
…z/el graphs

Opacity is already in the Display section so no need to repeat it in
composite or playback results. Playback controls (play/pause/step,
timeline slider, frame label) now appear below the sky dome and az/el
graphs. Added vstSweepControlsInline class for wider inline layout.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Entity (source) Select now appears in the shade item header between
the checkbox and close button. Export/download dropdown moved into the
Results collapsible section.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
… handler

1. ll2aerll(): fix kernel unload path mismatch (was using wrong
   package_dir + '/kernels/' prefix, now matches the load path)
2. ll2aerll.py bulk mode: exit with code 1 on error so Node returns
   500 instead of silently forwarding a dict as success
3. utils.js ll2aerll_bulk close handler: unconditional return after
   error block prevents stdout from leaking on non-zero exit

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- Link toggle icon with tooltip appears left of play controls
- Linked (default): playback synced across all linked shade maps
- Unlinked: independent timeline per shade item
- Unlinking seeds local index from current global position
- Play controls aligned to the left

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
1. Close handler now falls back to stdout (where Python prints
   structured error JSON) when stderr is empty
2. ll2aerll_bulk requests sent as JSON (Content-Type: application/json)
   instead of URL-encoded form to avoid body-parser's 1000 parameter
   limit when sending large time arrays

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- Playback link/unlink toggle moved to right side of play controls
- Export section now: Export label + Select dropdown (145px) + download icon
- Removed Dropdown import (no longer used)

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- vstTime section now shows Start Time, End Time, and Step Size
  (removed redundant vstSweepBody time inputs)
- Each shade item's Observer section now shows observer-local start/end
  time inputs when an observer is selected
- UTC <-> observer local time conversion via chronice API
- Editing observer time on blur converts back to UTC and updates
  the global sweep start/end times
- Added convertUTCToObserver and convertObserverToUTC to ShadeTool

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- ShadeTool.make() now initializes sweepStart/sweepEnd from TimeControl
  on mount and keeps them in sync via the TimeControl subscription
- Removed duplicate ShadeTool_Sweep subscription from ShadePanel
  (single source of truth is now ShadeTool's subscription)
- Observer local times update reactively since they depend on
  sweepStart/sweepEnd via useEffect

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Raw Python tracebacks were being sent to the client in the error
message field. Now only the generic exit code is returned; stderr
is still logged server-side for debugging.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Apply encodeURIComponent to target, obsRefFrame, and obsBody before
passing to Python, matching the single ll2aerll endpoint's approach.
Prevents directory traversal via crafted body/target values used in
kernel path construction.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Add unquote() calls for target, obsRefFrame, and obsBody in bulk
mode to match single mode, since the Node endpoint now applies
encodeURIComponent to these fields.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- Add React-lifecycle TimeControl subscription in ShadePanel that keeps
  sweepStart/sweepEnd in sync with TimeUI (handles both Range and Point modes)
- Remove redundant external sync from ShadeTool.make() subscription
  (single source of truth is now the component-level subscription)
- Observer times fall back to UTC display if chronice API is unavailable

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- CardLegend shows draggable handles at bin boundaries in discrete mode
- Dragging stops adjusts the width of each color bin in real-time
- Heatmap re-renders with custom stop positions (evalColorWithStops, getBinForValue)
- Reset icon appears when stops differ from defaults, restores even spacing
- colorStops stored per-element in sweepElData

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- Color stop handles show their % position on hover/drag
- Drag uses local state for instant visual preview of gradient bands
- Heatmap re-render only fires on mouse release (not during drag)
  for better performance with large heatmaps

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- CardLegend: use ref to track drag stops, fire onColorStopsChange via
  setTimeout after state settles (not inside state updater)
- _onPanEnd: skip auto-regeneration of static shade for elements in
  composite/playback mode; panning only marks sweep as stale (re-enables
  the Sweep button)

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
devin-ai-integration Bot and others added 26 commits June 9, 2026 22:10
Both the Earth (USGS SF Hill) and Lunar South Pole (LRO LOLA 4000m)
DEMs are now tiled COGs with deflate compression and overviews.
This ensures consistent behavior with the sightmap COG requirement
and enables fast overview-based reads at any resolution.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
…server time sync

- chronice.py: add lunar LSMT support using SPICE et2lst with observer
  longitude; format: LDAY-NNNNNLHH:MM:SS; reverse conversion via iterative
  refinement
- utils.js: pass optional lng param through to chronice.py
- SightlineTool.js: remove TimeUI indicator on mode switch, cancel sweep,
  resweep start, and pan-end; pass lng from observer point for LSMT observers
- SightlineElement.jsx: update global TimeControl when observer time inputs
  are changed (blur/Enter), fixing Mars SOL time not updating the TimeUI
- Lunar ref mission config: add Moon (LSMT) observer with type=lsmt

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
…key on observer time

- _getObserverLng: fall back to map center when indicatorLastDragPoint is null
- SightlineElement: hide DEM dropdown when no data options configured
- SightlineElement: add onKeyDown Enter handler on observer time inputs

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
…e config descriptions

- Only use _projImageOverlay and viewport clipping when the mission uses
  a custom projected CRS (projection.custom=true). For standard longlat/
  Mercator missions (like Mars), the DEM's projected bounds are in a
  different CRS than the map, causing misplaced overlays.
- Restore Layer-specific DEMs config row and improve field descriptions
  in sightline tool config.json (lost during ShadeTool→SightlineTool rename).
- Add sweepColorRamps and observer type examples to descriptionFull.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
…a row, clear default name

- Remove Layer-specific DEMs config row (previously asked to remove)
- Restore detailed descriptions from old ShadeTool config:
  - Sources: documents name/value properties, dropdown usage, kernel path
  - Observers: documents name/value/frame/body, chronos setup path
  - Default Height: full description of height parameter behavior
  - Observer Time Placeholder: documents format string usage
  - Frame/Body fields: proper SPICE reference descriptions
- Remove 'Sightline N' default element name (now empty)

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
…instead of UTC

Root cause: chronice lmst→utc returns '2026-05-30T21:36:57.975' (no Z suffix).
The old ShadeTool correctly did: result.replace(' ', 'T') + 'Z'
The new code used a regex chain that failed when milliseconds were present
without a trailing Z, leaving the string timezone-ambiguous. new Date()
then parsed it as local time (UTC-7), adding ~7 hours.

Fix: strip milliseconds then unconditionally append Z, matching the old
ShadeTool approach. The /ZZ$/ → Z guard prevents double-Z if chronice
ever returns a Z-suffixed result in the future.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
…t, tab-switch regen, add editable time field

1. Sightmap sun direction: _compute_directions now only applies convergence
   rotation for azimuthal projections (stereo/gnomonic). For cylindrical
   projections (Equidistant Cylindrical, Mercator), grid north = geographic
   north so convergence = 0. Previously applied polar-stereo formula to all
   projected CRS, giving ~90deg rotation on Mars DEM.

2. Observer time 1-second drift: restored _lastConvertedMs pattern from old
   ShadeTool. Saves sub-second precision from observer->UTC conversion and
   re-attaches it in UTC->observer reverse conversion for exact round-trips.

3. Tab-switch regeneration: _onTimeChange now tracks _lastGeneratedTime and
   skips if unchanged, preventing redundant sightmap computation when
   TimeControl re-broadcasts the same time on tab refocus.

4. Editable time field (vstOptionTime): restored from old ShadeTool. Shows
   current end time in configured format (DOY, etc), editable on blur/Enter.
   Parses via utcTimeFormat if configured, else appends Z directly.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- CSS matches old ShadeTool exactly: full-width centered input, bold 14px,
  color-p0 bg, color-a1-5 text, transparent border that shows color-c on focus
- Clock icon positioned absolute right (pointer-events: none) as in original
- Structure uses flexbetween wrapper matching old jQuery markup
- Mars reference mission utcTimeFormat changed to DOY: '%Y-%j %H:%M:%S'
  giving output like '2026-150 21:36:57' instead of ISO format

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
…text non-selectable

1. HorizonProfile.py _grid_convergence: same fix as sightmap.py — only
   apply convergence for azimuthal projections (stereo/gnomonic). For
   cylindrical projections (Mars Equidist. Cylindrical), convergence = 0,
   so horizon terrain profile azimuths are now correct.

2. Visibility chart (.sightlineVisWrap): added user-select: none so
   dragging the timeline scrubber doesn't accidentally highlight text.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
…ing/lag

- HorizonProfile.py: compute per-axis pixel scales (px_scale_x, px_scale_y)
  so the march direction accounts for longitude compression at observer
  latitude. For geographic CRS at 38°N, 1° lon ≈ 0.79 × 1° lat in meters;
  without this the march traces wrong physical angles, distorting azimuths.
  Also computes correct per-step physical distance instead of using the
  averaged pixel_scale.

- Crosshair restyled: smaller (8px circle, 5px arms), lime green with
  black borders (box-shadow outline).

- Crosshair converted from raw DOM element to Leaflet DivIcon marker.
  Leaflet handles positioning in its own transform pipeline, eliminating
  the lag that occurred when updating CSS left/top on the move event.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
…is open

Small 6px lime green dot with 1px black border, always at 50%/50% of
the map container (CSS-only positioning, no event tracking needed).
Added on make(), removed on destroy().

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Previously the crosshair only corrected its position on the next pan
event. Now _updateCrosshairPosition() is called right after sweepCenter
is stored for both static sightmap and batch/sweep completion.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
- Add SAFE_NAME_RE validation on target, obsRefFrame, obsBody to prevent
  directory traversal via SPICE kernel paths (matches /ll2aerll_bulk).
- Add MAX_TIMES=200 cap on sightmap batch to prevent resource exhaustion.
- Fix E2E test: batch response is a raw JSON array, not { results: [...] }.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Match ll2aerll_bulk pattern: handle child.on('error') and
child.stdin.on('error') to prevent hung responses if Python
fails to start. Add !res.headersSent guards on all response
paths in the close handler.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
After open_dem decimates a large DEM, gt[5] is scaled but ds still has
the original RasterYSize. Using ds.RasterYSize with the decimated gt
produces a wrong mid_lat for geographic CRS pixel scale. Now accepts
dem_rows directly from dem.shape.

Also: encodeURIComponent the chronice lng argument to match the other
CLI args (consistency with unquote() on the Python side).

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
cross(normal, north) yields West, not East. Changed to
cross(north, normal) to match the batch version _sun_azel_batch.
Currently unused at runtime but prevents future bugs.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Removed unused scalar functions that were superseded by vectorized
equivalents: sun_azel_at_cell (replaced by _sun_azel_batch),
is_nodata (replaced by _vectorized_is_nodata), geo_to_pixel (never
called). Also removed the unused ds parameter from open_dem return
value and _compute_bounds signature — ds was only kept alive for
get_pixel_scale which no longer needs it after the dem_rows fix.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
SightlineTool.js: removed showSightlinemapLayers, showSweepLayers,
refreshAllHeatmaps, _nextPow2 — all defined but never called.

SightlineTool_Algorithm.js: removed the entire old client-side
sightline algorithm (sightline, processUp/Down, mask, curveData,
isNoData, compositeResults, calcHeight*, initializeGrids, perOctant)
and their unused imports (jquery, F_, L_, G_). Only
cumulativeVisibility is called externally; all other methods were
from the pre-backend era and superseded by sightmap.py.

SightlineTool_Graphs.js: removed _localNorthAngle, replaced by
the geodesic _destinationPoint + _azimuthEndpoint approach.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Remove overflow:hidden from vstSightlineItem, vstSweepCard, and
vstSweepCardsSection so absolutely-positioned color picker palettes
and color ramp dropdowns are no longer clipped by their parent
containers. Add border-radius to headers directly to preserve
rounded corners.

Bump vstColorPalette z-index from 100 to 10000 to match the
ColorRampPicker popup z-index.

Reorder MULTI_SOURCE_COLORS: yellow -> blue -> red -> green
(swapped blue and red positions).

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
et2lst returns integer (hr, mn, sc) so one lunar second spans ~29 ET
seconds. The old iterative loop converged to ±1 lunar second, giving
~30s UTC precision. Now uses binary search after the coarse loop to
find the exact ET boundary where the second ticks over, narrowing to
<0.5 ET seconds. Result is placed at the midpoint of the lunar
second window for minimal round-trip error.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
The Collapsible panel has overflow:hidden for its open/close
animation, which clips the color picker dropdown. Changed the
palette to position:fixed, computed from the swatch's bounding
rect on click, so it escapes all overflow containers.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
Reverts position:fixed approach. Instead overrides overflow to
visible on open Collapsible panels inside sightlineTool via
[data-open] selector, so the color palette can extend past the
panel boundary while keeping overflow:hidden during animations.

Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com>
…-config

feat: SightlineTool + backend sightmap + polar projection fixes + Numba JIT
@tariqksoliman tariqksoliman self-assigned this Jun 11, 2026
@tariqksoliman tariqksoliman added the enhancement For making an existing feature better label Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement For making an existing feature better

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant