1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
16} from '@mui/material';
17import { useState } from 'react';
18import { GlobalParameters, useScenarioStore } from '@/stores/scenarioStore';
19import { deriveUtmZone } from '@/stores/scenarioStore/serializers';
20import { BufferedTextField, NumberField, Section } from './InspectorControls';
22interface GlobalParametersInspectorProps {
23 item: GlobalParameters;
26export function GlobalParametersInspector({
28}: GlobalParametersInspectorProps) {
29 const updateItem = useScenarioStore((s) => s.updateItem);
30 const setRotationAngleUnit = useScenarioStore(
31 (s) => s.setRotationAngleUnit
33 const platforms = useScenarioStore((s) => s.platforms);
34 const handleChange = (path: string, value: unknown) =>
35 updateItem(item.id, path, value);
36 const [pendingRotationUnit, setPendingRotationUnit] = useState<
37 GlobalParameters['rotationAngleUnit'] | null
40 const hasExistingRotationValues = platforms.some((platform) => {
41 if (platform.rotation.type === 'fixed') {
43 platform.rotation.startAzimuth,
44 platform.rotation.startElevation,
45 platform.rotation.azimuthRate,
46 platform.rotation.elevationRate,
47 ].some((value) => value !== 0);
49 return platform.rotation.waypoints.some(
50 (waypoint) => waypoint.azimuth !== 0 || waypoint.elevation !== 0
54 const handleRotationUnitChange = (
55 nextUnit: GlobalParameters['rotationAngleUnit']
57 if (nextUnit === item.rotationAngleUnit) {
60 if (hasExistingRotationValues) {
61 setPendingRotationUnit(nextUnit);
64 setRotationAngleUnit(nextUnit, false);
67 const handleCoordinateSystemFrameChange = (
68 frame: GlobalParameters['coordinateSystem']['frame']
70 if (frame === 'UTM') {
71 handleChange('coordinateSystem', {
74 item.coordinateSystem.zone ??
75 deriveUtmZone(item.origin.longitude),
77 item.coordinateSystem.hemisphere ??
78 (item.origin.latitude < 0 ? 'S' : 'N'),
83 handleChange('coordinateSystem', { frame });
87 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
88 <FormControl fullWidth size="small">
89 <InputLabel>Rotation Angle Unit</InputLabel>
91 label="Rotation Angle Unit"
92 value={item.rotationAngleUnit}
94 handleRotationUnitChange(
96 .value as GlobalParameters['rotationAngleUnit']
100 <MenuItem value="deg">Degrees</MenuItem>
101 <MenuItem value="rad">Radians</MenuItem>
105 label="Simulation Name"
109 value={item.simulation_name}
111 onChange={(v) => handleChange('simulation_name', v)}
114 label="Start Time (s)"
116 emptyBehavior="revert"
117 onChange={(v) => handleChange('start', v)}
122 emptyBehavior="revert"
123 onChange={(v) => handleChange('end', v)}
126 label="Output Sampling Rate (Hz)"
128 emptyBehavior="revert"
129 onChange={(v) => handleChange('rate', v)}
132 label="Internal Sim Sampling Rate (Hz)"
133 value={item.simSamplingRate}
134 emptyBehavior="revert"
135 onChange={(v) => handleChange('simSamplingRate', v)}
138 label="Speed of Light (m/s)"
140 emptyBehavior="revert"
141 onChange={(v) => handleChange('c', v)}
145 value={item.random_seed}
147 onChange={(v) => handleChange('random_seed', v)}
151 value={item.adc_bits}
152 emptyBehavior="revert"
153 onChange={(v) => handleChange('adc_bits', v)}
156 label="Oversample Ratio"
157 value={item.oversample_ratio}
158 emptyBehavior="revert"
159 onChange={(v) => handleChange('oversample_ratio', v)}
162 <Section title="KML / Geospatial Export">
164 label="KML Origin Latitude (deg)"
165 value={item.origin.latitude}
166 emptyBehavior="revert"
167 onChange={(v) => handleChange('origin.latitude', v)}
170 label="KML Origin Longitude (deg)"
171 value={item.origin.longitude}
172 emptyBehavior="revert"
173 onChange={(v) => handleChange('origin.longitude', v)}
176 label="KML Origin Altitude (m)"
177 value={item.origin.altitude}
178 emptyBehavior="revert"
179 onChange={(v) => handleChange('origin.altitude', v)}
181 <FormControl fullWidth size="small">
182 <InputLabel>KML Coordinate System</InputLabel>
184 label="KML Coordinate System"
185 value={item.coordinateSystem.frame}
187 handleCoordinateSystemFrameChange(
189 .value as GlobalParameters['coordinateSystem']['frame']
193 <MenuItem value="ENU">ENU (East-North-Up)</MenuItem>
194 <MenuItem value="UTM">UTM</MenuItem>
195 <MenuItem value="ECEF">ECEF</MenuItem>
198 {item.coordinateSystem.frame === 'UTM' && (
202 value={item.coordinateSystem.zone ?? null}
203 emptyBehavior="revert"
205 handleChange('coordinateSystem.zone', v)
208 <FormControl fullWidth size="small">
209 <InputLabel>KML UTM Hemisphere</InputLabel>
211 label="KML UTM Hemisphere"
212 value={item.coordinateSystem.hemisphere ?? 'N'}
215 'coordinateSystem.hemisphere',
220 <MenuItem value="N">North</MenuItem>
221 <MenuItem value="S">South</MenuItem>
228 open={pendingRotationUnit !== null}
229 onClose={() => setPendingRotationUnit(null)}
231 <DialogTitle>Change Rotation Angle Unit</DialogTitle>
234 Existing rotation values are present. Convert them to
235 keep the same physical orientation and rates, or keep
236 the numeric values as they are.
240 <Button onClick={() => setPendingRotationUnit(null)}>
245 if (pendingRotationUnit) {
246 setRotationAngleUnit(
251 setPendingRotationUnit(null);
259 if (pendingRotationUnit) {
260 setRotationAngleUnit(pendingRotationUnit, true);
262 setPendingRotationUnit(null);
265 Convert Existing Values