SAR Search Pattern Update
This commit is contained in:
@@ -50,7 +50,7 @@ Returns a JSON array of all indexed simulations, including their ID, name, creat
|
||||
|
||||
### `POST /api/simulations/create`
|
||||
Creates a new simulation entry.
|
||||
- **Request Body:** JSON object containing the combined configurations (e.g., `search`, `uav`, `ugv`). The configuration *must* contain at least one search pattern definition (either `spiral`, `lawnmower`, or `levy`) inside the JSON.
|
||||
- **Request Body:** JSON object containing the combined configurations (e.g., `search`, `uav`, `ugv`). The configuration *must* contain at least one search pattern definition (either `spiral`, `lawnmower`, `levy`, or `sar`) inside the JSON.
|
||||
- **Behavior:** Checks if an identical config exists. If so, it returns the existing simulation ID/Name for overwriting. If not, it allocates a new `simulation_X` incrementally, inserts it into the database, builds the file directory in `../results`, and returns the new ID/Name. Returns `400 Bad Request` if payload is not valid JSON or lacks a known search pattern.
|
||||
- **Response:** JSON object `{"id": <int>, "name": <string>}`
|
||||
|
||||
|
||||
@@ -48,12 +48,15 @@ func (rt *Router) CreateSimulation(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := searchMap["levy"]; ok {
|
||||
hasPattern = true
|
||||
}
|
||||
if _, ok := searchMap["sar"]; ok {
|
||||
hasPattern = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasPattern {
|
||||
http.Error(w, "Simulation configuration must include a search pattern (spiral, lawnmower, or levy)", http.StatusBadRequest)
|
||||
http.Error(w, "Simulation configuration must include a search pattern (spiral, lawnmower, levy, or sar)", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
<option value="spiral">Spiral</option>
|
||||
<option value="lawnmower">Lawnmower</option>
|
||||
<option value="levy">Levy</option>
|
||||
<option value="sar">SAR</option>
|
||||
</select>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -28,7 +28,9 @@
|
||||
<td>{sim.id}</td>
|
||||
<td>{sim.name}</td>
|
||||
<td style="text-transform: capitalize;"
|
||||
>{parseConfig(sim.config).pattern || "Unknown"}</td
|
||||
>{parseConfig(sim.config).pattern === "sar"
|
||||
? "SAR"
|
||||
: parseConfig(sim.config).pattern || "Unknown"}</td
|
||||
>
|
||||
<td>{formatTime(sim.search_time || NaN)}</td>
|
||||
<td>{formatTime(sim.total_time || NaN)}</td>
|
||||
|
||||
@@ -29,6 +29,7 @@ export function parseConfig(config: string) {
|
||||
if (searchObj.spiral) pattern = "spiral";
|
||||
else if (searchObj.lawnmower) pattern = "lawnmower";
|
||||
else if (searchObj.levy) pattern = "levy";
|
||||
else if (searchObj.sar) pattern = "sar";
|
||||
|
||||
return {
|
||||
altitude: searchObj.altitude != null ? parseFloat(searchObj.altitude) : null,
|
||||
|
||||
@@ -102,9 +102,7 @@
|
||||
simulation && simulation.resources
|
||||
? simulation.resources.find(
|
||||
(res: string) =>
|
||||
res.includes("camera") &&
|
||||
!res.includes("ugv") &&
|
||||
isVideo(res),
|
||||
res.includes("camera") && !res.includes("ugv") && isVideo(res),
|
||||
)
|
||||
: undefined,
|
||||
);
|
||||
@@ -220,9 +218,7 @@
|
||||
|
||||
xhr.upload.onprogress = (event) => {
|
||||
if (event.lengthComputable) {
|
||||
uploadProgress = Math.round(
|
||||
(event.loaded / event.total) * 100,
|
||||
);
|
||||
uploadProgress = Math.round((event.loaded / event.total) * 100);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -230,15 +226,11 @@
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(JSON.parse(xhr.responseText));
|
||||
} else {
|
||||
reject(
|
||||
xhr.responseText ||
|
||||
`Failed to upload: ${file.name}`,
|
||||
);
|
||||
reject(xhr.responseText || `Failed to upload: ${file.name}`);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () =>
|
||||
reject(`Network error uploading: ${file.name}`);
|
||||
xhr.onerror = () => reject(`Network error uploading: ${file.name}`);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
@@ -246,10 +238,7 @@
|
||||
});
|
||||
|
||||
if (!simulation.resources.includes(upData.filename)) {
|
||||
simulation.resources = [
|
||||
...simulation.resources,
|
||||
upData.filename,
|
||||
];
|
||||
simulation.resources = [...simulation.resources, upData.filename];
|
||||
}
|
||||
} catch (err: any) {
|
||||
saveError = err.toString();
|
||||
@@ -304,9 +293,7 @@
|
||||
}
|
||||
async function handleDeleteResource(resourceName: string) {
|
||||
if (
|
||||
!confirm(
|
||||
`Are you sure you want to permanently delete ${resourceName}?`,
|
||||
)
|
||||
!confirm(`Are you sure you want to permanently delete ${resourceName}?`)
|
||||
)
|
||||
return;
|
||||
const res = await fetch(
|
||||
@@ -337,14 +324,10 @@
|
||||
{#if !isEditing}
|
||||
<h1>Simulation Detail: {simulation.name}</h1>
|
||||
<div class="header-actions no-print">
|
||||
<button class="export-btn" onclick={handleExportPDF}
|
||||
>Export PDF</button
|
||||
>
|
||||
<button class="edit-btn" onclick={() => (isEditing = true)}
|
||||
>Edit</button
|
||||
>
|
||||
<button class="delete-btn" onclick={handleDelete}>Delete</button
|
||||
<button class="export-btn" onclick={handleExportPDF}>Export PDF</button>
|
||||
<button class="edit-btn" onclick={() => (isEditing = true)}>Edit</button
|
||||
>
|
||||
<button class="delete-btn" onclick={handleDelete}>Delete</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="edit-form">
|
||||
@@ -382,35 +365,23 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sim-files">Upload Media:</label>
|
||||
<input
|
||||
id="sim-files"
|
||||
type="file"
|
||||
multiple
|
||||
bind:files={uploadFiles}
|
||||
/>
|
||||
<input id="sim-files" type="file" multiple bind:files={uploadFiles} />
|
||||
</div>
|
||||
{#if saveError}
|
||||
<p class="error">{saveError}</p>
|
||||
{/if}
|
||||
{#if isUploading}
|
||||
<div class="progress-bar-container">
|
||||
<div
|
||||
class="progress-bar"
|
||||
style={`width: ${uploadProgress}%`}
|
||||
></div>
|
||||
<div class="progress-bar" style={`width: ${uploadProgress}%`}></div>
|
||||
<span class="progress-text">{uploadProgress}%</span>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="form-actions">
|
||||
<button
|
||||
class="save-btn"
|
||||
onclick={handleSave}
|
||||
disabled={isUploading}>Save</button
|
||||
<button class="save-btn" onclick={handleSave} disabled={isUploading}
|
||||
>Save</button
|
||||
>
|
||||
<button
|
||||
class="cancel-btn"
|
||||
onclick={cancelEdit}
|
||||
disabled={isUploading}>Cancel</button
|
||||
<button class="cancel-btn" onclick={cancelEdit} disabled={isUploading}
|
||||
>Cancel</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -430,6 +401,8 @@
|
||||
? "Lawnmower"
|
||||
: parsedConfig?.find((c) => c.key === "search.levy.max_steps")
|
||||
? "Levy"
|
||||
: parsedConfig?.find((c) => c.key.startsWith("search.sar"))
|
||||
? "SAR"
|
||||
: "Unknown"}</span
|
||||
>
|
||||
</p>
|
||||
@@ -474,19 +447,11 @@
|
||||
<h3 class="table-label">General</h3>
|
||||
<table class="config-table">
|
||||
<thead>
|
||||
<tr
|
||||
><th>Parameter</th><th
|
||||
>Value</th
|
||||
></tr
|
||||
>
|
||||
<tr><th>Parameter</th><th>Value</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each groups["_general"] as item}
|
||||
<tr
|
||||
><td>{item.key}</td><td
|
||||
>{item.value}</td
|
||||
></tr
|
||||
>
|
||||
<tr><td>{item.key}</td><td>{item.value}</td></tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -494,27 +459,16 @@
|
||||
{/if}
|
||||
{#each Object.entries(groups).filter(([k]) => k !== "_general") as [groupName, items]}
|
||||
<div class="table-wrapper">
|
||||
<h3
|
||||
class="table-label"
|
||||
style="text-transform: capitalize;"
|
||||
>
|
||||
<h3 class="table-label" style="text-transform: capitalize;">
|
||||
{groupName}
|
||||
</h3>
|
||||
<table class="config-table">
|
||||
<thead>
|
||||
<tr
|
||||
><th>Parameter</th><th
|
||||
>Value</th
|
||||
></tr
|
||||
>
|
||||
<tr><th>Parameter</th><th>Value</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each items as item}
|
||||
<tr
|
||||
><td>{item.key}</td><td
|
||||
>{item.value}</td
|
||||
></tr
|
||||
>
|
||||
<tr><td>{item.key}</td><td>{item.value}</td></tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -104,14 +104,12 @@
|
||||
<span style="text-transform: capitalize;">
|
||||
{parsedConfig?.find((c) => c.key === "search.spiral.max_legs")
|
||||
? "Spiral"
|
||||
: parsedConfig?.find(
|
||||
(c) => c.key === "search.lawnmower.width",
|
||||
)
|
||||
: parsedConfig?.find((c) => c.key === "search.lawnmower.width")
|
||||
? "Lawnmower"
|
||||
: parsedConfig?.find(
|
||||
(c) => c.key === "search.levy.max_steps",
|
||||
)
|
||||
: parsedConfig?.find((c) => c.key === "search.levy.max_steps")
|
||||
? "Levy"
|
||||
: parsedConfig?.find((c) => c.key.startsWith("search.sar"))
|
||||
? "SAR"
|
||||
: "Unknown"}
|
||||
</span>
|
||||
</p>
|
||||
@@ -156,19 +154,11 @@
|
||||
<h3 class="table-label">General</h3>
|
||||
<table class="config-table">
|
||||
<thead>
|
||||
<tr
|
||||
><th>Parameter</th><th
|
||||
>Value</th
|
||||
></tr
|
||||
>
|
||||
<tr><th>Parameter</th><th>Value</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each groups["_general"] as item}
|
||||
<tr
|
||||
><td>{item.key}</td><td
|
||||
>{item.value}</td
|
||||
></tr
|
||||
>
|
||||
<tr><td>{item.key}</td><td>{item.value}</td></tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -176,27 +166,16 @@
|
||||
{/if}
|
||||
{#each Object.entries(groups).filter(([k]) => k !== "_general") as [groupName, items]}
|
||||
<div class="table-wrapper">
|
||||
<h3
|
||||
class="table-label"
|
||||
style="text-transform: capitalize;"
|
||||
>
|
||||
<h3 class="table-label" style="text-transform: capitalize;">
|
||||
{groupName}
|
||||
</h3>
|
||||
<table class="config-table">
|
||||
<thead>
|
||||
<tr
|
||||
><th>Parameter</th><th
|
||||
>Value</th
|
||||
></tr
|
||||
>
|
||||
<tr><th>Parameter</th><th>Value</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each items as item}
|
||||
<tr
|
||||
><td>{item.key}</td><td
|
||||
>{item.value}</td
|
||||
></tr
|
||||
>
|
||||
<tr><td>{item.key}</td><td>{item.value}</td></tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -221,10 +200,7 @@
|
||||
<div class="image-grid">
|
||||
{#each imagesList as image}
|
||||
<div class="image-wrapper">
|
||||
<img
|
||||
src={getResourceUrl(simulation.name, image)}
|
||||
alt={image}
|
||||
/>
|
||||
<img src={getResourceUrl(simulation.name, image)} alt={image} />
|
||||
<p class="caption">{image}</p>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
Reference in New Issue
Block a user