FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
GlobalParametersInspector.tsx
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
3
4import {
5 Box,
6 Button,
7 Dialog,
8 DialogActions,
9 DialogContent,
10 DialogContentText,
11 DialogTitle,
12 FormControl,
13 InputLabel,
14 MenuItem,
15 Select,
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';
21
22interface GlobalParametersInspectorProps {
23 item: GlobalParameters;
24}
25
26export function GlobalParametersInspector({
27 item,
28}: GlobalParametersInspectorProps) {
29 const updateItem = useScenarioStore((s) => s.updateItem);
30 const setRotationAngleUnit = useScenarioStore(
31 (s) => s.setRotationAngleUnit
32 );
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
38 >(null);
39
40 const hasExistingRotationValues = platforms.some((platform) => {
41 if (platform.rotation.type === 'fixed') {
42 return [
43 platform.rotation.startAzimuth,
44 platform.rotation.startElevation,
45 platform.rotation.azimuthRate,
46 platform.rotation.elevationRate,
47 ].some((value) => value !== 0);
48 }
49 return platform.rotation.waypoints.some(
50 (waypoint) => waypoint.azimuth !== 0 || waypoint.elevation !== 0
51 );
52 });
53
54 const handleRotationUnitChange = (
55 nextUnit: GlobalParameters['rotationAngleUnit']
56 ) => {
57 if (nextUnit === item.rotationAngleUnit) {
58 return;
59 }
60 if (hasExistingRotationValues) {
61 setPendingRotationUnit(nextUnit);
62 return;
63 }
64 setRotationAngleUnit(nextUnit, false);
65 };
66
67 const handleCoordinateSystemFrameChange = (
68 frame: GlobalParameters['coordinateSystem']['frame']
69 ) => {
70 if (frame === 'UTM') {
71 handleChange('coordinateSystem', {
72 frame,
73 zone:
74 item.coordinateSystem.zone ??
75 deriveUtmZone(item.origin.longitude),
76 hemisphere:
77 item.coordinateSystem.hemisphere ??
78 (item.origin.latitude < 0 ? 'S' : 'N'),
79 });
80 return;
81 }
82
83 handleChange('coordinateSystem', { frame });
84 };
85
86 return (
87 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
88 <FormControl fullWidth size="small">
89 <InputLabel>Rotation Angle Unit</InputLabel>
90 <Select
91 label="Rotation Angle Unit"
92 value={item.rotationAngleUnit}
93 onChange={(e) =>
94 handleRotationUnitChange(
95 e.target
96 .value as GlobalParameters['rotationAngleUnit']
97 )
98 }
99 >
100 <MenuItem value="deg">Degrees</MenuItem>
101 <MenuItem value="rad">Radians</MenuItem>
102 </Select>
103 </FormControl>
104 <BufferedTextField
105 label="Simulation Name"
106 variant="outlined"
107 size="small"
108 fullWidth
109 value={item.simulation_name}
110 allowEmpty={false}
111 onChange={(v) => handleChange('simulation_name', v)}
112 />
113 <NumberField
114 label="Start Time (s)"
115 value={item.start}
116 emptyBehavior="revert"
117 onChange={(v) => handleChange('start', v)}
118 />
119 <NumberField
120 label="End Time (s)"
121 value={item.end}
122 emptyBehavior="revert"
123 onChange={(v) => handleChange('end', v)}
124 />
125 <NumberField
126 label="Output Sampling Rate (Hz)"
127 value={item.rate}
128 emptyBehavior="revert"
129 onChange={(v) => handleChange('rate', v)}
130 />
131 <NumberField
132 label="Internal Sim Sampling Rate (Hz)"
133 value={item.simSamplingRate}
134 emptyBehavior="revert"
135 onChange={(v) => handleChange('simSamplingRate', v)}
136 />
137 <NumberField
138 label="Speed of Light (m/s)"
139 value={item.c}
140 emptyBehavior="revert"
141 onChange={(v) => handleChange('c', v)}
142 />
143 <NumberField
144 label="Random Seed"
145 value={item.random_seed}
146 emptyBehavior="null"
147 onChange={(v) => handleChange('random_seed', v)}
148 />
149 <NumberField
150 label="ADC Bits"
151 value={item.adc_bits}
152 emptyBehavior="revert"
153 onChange={(v) => handleChange('adc_bits', v)}
154 />
155 <NumberField
156 label="Oversample Ratio"
157 value={item.oversample_ratio}
158 emptyBehavior="revert"
159 onChange={(v) => handleChange('oversample_ratio', v)}
160 />
161
162 <Section title="KML / Geospatial Export">
163 <NumberField
164 label="KML Origin Latitude (deg)"
165 value={item.origin.latitude}
166 emptyBehavior="revert"
167 onChange={(v) => handleChange('origin.latitude', v)}
168 />
169 <NumberField
170 label="KML Origin Longitude (deg)"
171 value={item.origin.longitude}
172 emptyBehavior="revert"
173 onChange={(v) => handleChange('origin.longitude', v)}
174 />
175 <NumberField
176 label="KML Origin Altitude (m)"
177 value={item.origin.altitude}
178 emptyBehavior="revert"
179 onChange={(v) => handleChange('origin.altitude', v)}
180 />
181 <FormControl fullWidth size="small">
182 <InputLabel>KML Coordinate System</InputLabel>
183 <Select
184 label="KML Coordinate System"
185 value={item.coordinateSystem.frame}
186 onChange={(e) =>
187 handleCoordinateSystemFrameChange(
188 e.target
189 .value as GlobalParameters['coordinateSystem']['frame']
190 )
191 }
192 >
193 <MenuItem value="ENU">ENU (East-North-Up)</MenuItem>
194 <MenuItem value="UTM">UTM</MenuItem>
195 <MenuItem value="ECEF">ECEF</MenuItem>
196 </Select>
197 </FormControl>
198 {item.coordinateSystem.frame === 'UTM' && (
199 <>
200 <NumberField
201 label="KML UTM Zone"
202 value={item.coordinateSystem.zone ?? null}
203 emptyBehavior="revert"
204 onChange={(v) =>
205 handleChange('coordinateSystem.zone', v)
206 }
207 />
208 <FormControl fullWidth size="small">
209 <InputLabel>KML UTM Hemisphere</InputLabel>
210 <Select
211 label="KML UTM Hemisphere"
212 value={item.coordinateSystem.hemisphere ?? 'N'}
213 onChange={(e) =>
214 handleChange(
215 'coordinateSystem.hemisphere',
216 e.target.value
217 )
218 }
219 >
220 <MenuItem value="N">North</MenuItem>
221 <MenuItem value="S">South</MenuItem>
222 </Select>
223 </FormControl>
224 </>
225 )}
226 </Section>
227 <Dialog
228 open={pendingRotationUnit !== null}
229 onClose={() => setPendingRotationUnit(null)}
230 >
231 <DialogTitle>Change Rotation Angle Unit</DialogTitle>
232 <DialogContent>
233 <DialogContentText>
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.
237 </DialogContentText>
238 </DialogContent>
239 <DialogActions>
240 <Button onClick={() => setPendingRotationUnit(null)}>
241 Cancel
242 </Button>
243 <Button
244 onClick={() => {
245 if (pendingRotationUnit) {
246 setRotationAngleUnit(
247 pendingRotationUnit,
248 false
249 );
250 }
251 setPendingRotationUnit(null);
252 }}
253 >
254 Keep Numeric Values
255 </Button>
256 <Button
257 variant="contained"
258 onClick={() => {
259 if (pendingRotationUnit) {
260 setRotationAngleUnit(pendingRotationUnit, true);
261 }
262 setPendingRotationUnit(null);
263 }}
264 >
265 Convert Existing Values
266 </Button>
267 </DialogActions>
268 </Dialog>
269 </Box>
270 );
271}