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`
|
### `POST /api/simulations/create`
|
||||||
Creates a new simulation entry.
|
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.
|
- **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>}`
|
- **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 {
|
if _, ok := searchMap["levy"]; ok {
|
||||||
hasPattern = true
|
hasPattern = true
|
||||||
}
|
}
|
||||||
|
if _, ok := searchMap["sar"]; ok {
|
||||||
|
hasPattern = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasPattern {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
<option value="spiral">Spiral</option>
|
<option value="spiral">Spiral</option>
|
||||||
<option value="lawnmower">Lawnmower</option>
|
<option value="lawnmower">Lawnmower</option>
|
||||||
<option value="levy">Levy</option>
|
<option value="levy">Levy</option>
|
||||||
|
<option value="sar">SAR</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -28,7 +28,9 @@
|
|||||||
<td>{sim.id}</td>
|
<td>{sim.id}</td>
|
||||||
<td>{sim.name}</td>
|
<td>{sim.name}</td>
|
||||||
<td style="text-transform: capitalize;"
|
<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.search_time || NaN)}</td>
|
||||||
<td>{formatTime(sim.total_time || NaN)}</td>
|
<td>{formatTime(sim.total_time || NaN)}</td>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export function parseConfig(config: string) {
|
|||||||
if (searchObj.spiral) pattern = "spiral";
|
if (searchObj.spiral) pattern = "spiral";
|
||||||
else if (searchObj.lawnmower) pattern = "lawnmower";
|
else if (searchObj.lawnmower) pattern = "lawnmower";
|
||||||
else if (searchObj.levy) pattern = "levy";
|
else if (searchObj.levy) pattern = "levy";
|
||||||
|
else if (searchObj.sar) pattern = "sar";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
altitude: searchObj.altitude != null ? parseFloat(searchObj.altitude) : null,
|
altitude: searchObj.altitude != null ? parseFloat(searchObj.altitude) : null,
|
||||||
|
|||||||
@@ -102,9 +102,7 @@
|
|||||||
simulation && simulation.resources
|
simulation && simulation.resources
|
||||||
? simulation.resources.find(
|
? simulation.resources.find(
|
||||||
(res: string) =>
|
(res: string) =>
|
||||||
res.includes("camera") &&
|
res.includes("camera") && !res.includes("ugv") && isVideo(res),
|
||||||
!res.includes("ugv") &&
|
|
||||||
isVideo(res),
|
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
);
|
);
|
||||||
@@ -220,9 +218,7 @@
|
|||||||
|
|
||||||
xhr.upload.onprogress = (event) => {
|
xhr.upload.onprogress = (event) => {
|
||||||
if (event.lengthComputable) {
|
if (event.lengthComputable) {
|
||||||
uploadProgress = Math.round(
|
uploadProgress = Math.round((event.loaded / event.total) * 100);
|
||||||
(event.loaded / event.total) * 100,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -230,15 +226,11 @@
|
|||||||
if (xhr.status >= 200 && xhr.status < 300) {
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
resolve(JSON.parse(xhr.responseText));
|
resolve(JSON.parse(xhr.responseText));
|
||||||
} else {
|
} else {
|
||||||
reject(
|
reject(xhr.responseText || `Failed to upload: ${file.name}`);
|
||||||
xhr.responseText ||
|
|
||||||
`Failed to upload: ${file.name}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = () =>
|
xhr.onerror = () => reject(`Network error uploading: ${file.name}`);
|
||||||
reject(`Network error uploading: ${file.name}`);
|
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
@@ -246,10 +238,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!simulation.resources.includes(upData.filename)) {
|
if (!simulation.resources.includes(upData.filename)) {
|
||||||
simulation.resources = [
|
simulation.resources = [...simulation.resources, upData.filename];
|
||||||
...simulation.resources,
|
|
||||||
upData.filename,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
saveError = err.toString();
|
saveError = err.toString();
|
||||||
@@ -304,9 +293,7 @@
|
|||||||
}
|
}
|
||||||
async function handleDeleteResource(resourceName: string) {
|
async function handleDeleteResource(resourceName: string) {
|
||||||
if (
|
if (
|
||||||
!confirm(
|
!confirm(`Are you sure you want to permanently delete ${resourceName}?`)
|
||||||
`Are you sure you want to permanently delete ${resourceName}?`,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
@@ -337,14 +324,10 @@
|
|||||||
{#if !isEditing}
|
{#if !isEditing}
|
||||||
<h1>Simulation Detail: {simulation.name}</h1>
|
<h1>Simulation Detail: {simulation.name}</h1>
|
||||||
<div class="header-actions no-print">
|
<div class="header-actions no-print">
|
||||||
<button class="export-btn" onclick={handleExportPDF}
|
<button class="export-btn" onclick={handleExportPDF}>Export PDF</button>
|
||||||
>Export PDF</button
|
<button class="edit-btn" onclick={() => (isEditing = true)}>Edit</button
|
||||||
>
|
|
||||||
<button class="edit-btn" onclick={() => (isEditing = true)}
|
|
||||||
>Edit</button
|
|
||||||
>
|
|
||||||
<button class="delete-btn" onclick={handleDelete}>Delete</button
|
|
||||||
>
|
>
|
||||||
|
<button class="delete-btn" onclick={handleDelete}>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="edit-form">
|
<div class="edit-form">
|
||||||
@@ -382,35 +365,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="sim-files">Upload Media:</label>
|
<label for="sim-files">Upload Media:</label>
|
||||||
<input
|
<input id="sim-files" type="file" multiple bind:files={uploadFiles} />
|
||||||
id="sim-files"
|
|
||||||
type="file"
|
|
||||||
multiple
|
|
||||||
bind:files={uploadFiles}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{#if saveError}
|
{#if saveError}
|
||||||
<p class="error">{saveError}</p>
|
<p class="error">{saveError}</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#if isUploading}
|
{#if isUploading}
|
||||||
<div class="progress-bar-container">
|
<div class="progress-bar-container">
|
||||||
<div
|
<div class="progress-bar" style={`width: ${uploadProgress}%`}></div>
|
||||||
class="progress-bar"
|
|
||||||
style={`width: ${uploadProgress}%`}
|
|
||||||
></div>
|
|
||||||
<span class="progress-text">{uploadProgress}%</span>
|
<span class="progress-text">{uploadProgress}%</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button
|
<button class="save-btn" onclick={handleSave} disabled={isUploading}
|
||||||
class="save-btn"
|
>Save</button
|
||||||
onclick={handleSave}
|
|
||||||
disabled={isUploading}>Save</button
|
|
||||||
>
|
>
|
||||||
<button
|
<button class="cancel-btn" onclick={cancelEdit} disabled={isUploading}
|
||||||
class="cancel-btn"
|
>Cancel</button
|
||||||
onclick={cancelEdit}
|
|
||||||
disabled={isUploading}>Cancel</button
|
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -430,6 +401,8 @@
|
|||||||
? "Lawnmower"
|
? "Lawnmower"
|
||||||
: parsedConfig?.find((c) => c.key === "search.levy.max_steps")
|
: parsedConfig?.find((c) => c.key === "search.levy.max_steps")
|
||||||
? "Levy"
|
? "Levy"
|
||||||
|
: parsedConfig?.find((c) => c.key.startsWith("search.sar"))
|
||||||
|
? "SAR"
|
||||||
: "Unknown"}</span
|
: "Unknown"}</span
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
@@ -474,19 +447,11 @@
|
|||||||
<h3 class="table-label">General</h3>
|
<h3 class="table-label">General</h3>
|
||||||
<table class="config-table">
|
<table class="config-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr
|
<tr><th>Parameter</th><th>Value</th></tr>
|
||||||
><th>Parameter</th><th
|
|
||||||
>Value</th
|
|
||||||
></tr
|
|
||||||
>
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each groups["_general"] as item}
|
{#each groups["_general"] as item}
|
||||||
<tr
|
<tr><td>{item.key}</td><td>{item.value}</td></tr>
|
||||||
><td>{item.key}</td><td
|
|
||||||
>{item.value}</td
|
|
||||||
></tr
|
|
||||||
>
|
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -494,27 +459,16 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each Object.entries(groups).filter(([k]) => k !== "_general") as [groupName, items]}
|
{#each Object.entries(groups).filter(([k]) => k !== "_general") as [groupName, items]}
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<h3
|
<h3 class="table-label" style="text-transform: capitalize;">
|
||||||
class="table-label"
|
|
||||||
style="text-transform: capitalize;"
|
|
||||||
>
|
|
||||||
{groupName}
|
{groupName}
|
||||||
</h3>
|
</h3>
|
||||||
<table class="config-table">
|
<table class="config-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr
|
<tr><th>Parameter</th><th>Value</th></tr>
|
||||||
><th>Parameter</th><th
|
|
||||||
>Value</th
|
|
||||||
></tr
|
|
||||||
>
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each items as item}
|
{#each items as item}
|
||||||
<tr
|
<tr><td>{item.key}</td><td>{item.value}</td></tr>
|
||||||
><td>{item.key}</td><td
|
|
||||||
>{item.value}</td
|
|
||||||
></tr
|
|
||||||
>
|
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -104,14 +104,12 @@
|
|||||||
<span style="text-transform: capitalize;">
|
<span style="text-transform: capitalize;">
|
||||||
{parsedConfig?.find((c) => c.key === "search.spiral.max_legs")
|
{parsedConfig?.find((c) => c.key === "search.spiral.max_legs")
|
||||||
? "Spiral"
|
? "Spiral"
|
||||||
: parsedConfig?.find(
|
: parsedConfig?.find((c) => c.key === "search.lawnmower.width")
|
||||||
(c) => c.key === "search.lawnmower.width",
|
|
||||||
)
|
|
||||||
? "Lawnmower"
|
? "Lawnmower"
|
||||||
: parsedConfig?.find(
|
: parsedConfig?.find((c) => c.key === "search.levy.max_steps")
|
||||||
(c) => c.key === "search.levy.max_steps",
|
|
||||||
)
|
|
||||||
? "Levy"
|
? "Levy"
|
||||||
|
: parsedConfig?.find((c) => c.key.startsWith("search.sar"))
|
||||||
|
? "SAR"
|
||||||
: "Unknown"}
|
: "Unknown"}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -156,19 +154,11 @@
|
|||||||
<h3 class="table-label">General</h3>
|
<h3 class="table-label">General</h3>
|
||||||
<table class="config-table">
|
<table class="config-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr
|
<tr><th>Parameter</th><th>Value</th></tr>
|
||||||
><th>Parameter</th><th
|
|
||||||
>Value</th
|
|
||||||
></tr
|
|
||||||
>
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each groups["_general"] as item}
|
{#each groups["_general"] as item}
|
||||||
<tr
|
<tr><td>{item.key}</td><td>{item.value}</td></tr>
|
||||||
><td>{item.key}</td><td
|
|
||||||
>{item.value}</td
|
|
||||||
></tr
|
|
||||||
>
|
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -176,27 +166,16 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each Object.entries(groups).filter(([k]) => k !== "_general") as [groupName, items]}
|
{#each Object.entries(groups).filter(([k]) => k !== "_general") as [groupName, items]}
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<h3
|
<h3 class="table-label" style="text-transform: capitalize;">
|
||||||
class="table-label"
|
|
||||||
style="text-transform: capitalize;"
|
|
||||||
>
|
|
||||||
{groupName}
|
{groupName}
|
||||||
</h3>
|
</h3>
|
||||||
<table class="config-table">
|
<table class="config-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr
|
<tr><th>Parameter</th><th>Value</th></tr>
|
||||||
><th>Parameter</th><th
|
|
||||||
>Value</th
|
|
||||||
></tr
|
|
||||||
>
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each items as item}
|
{#each items as item}
|
||||||
<tr
|
<tr><td>{item.key}</td><td>{item.value}</td></tr>
|
||||||
><td>{item.key}</td><td
|
|
||||||
>{item.value}</td
|
|
||||||
></tr
|
|
||||||
>
|
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -221,10 +200,7 @@
|
|||||||
<div class="image-grid">
|
<div class="image-grid">
|
||||||
{#each imagesList as image}
|
{#each imagesList as image}
|
||||||
<div class="image-wrapper">
|
<div class="image-wrapper">
|
||||||
<img
|
<img src={getResourceUrl(simulation.name, image)} alt={image} />
|
||||||
src={getResourceUrl(simulation.name, image)}
|
|
||||||
alt={image}
|
|
||||||
/>
|
|
||||||
<p class="caption">{image}</p>
|
<p class="caption">{image}</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
Reference in New Issue
Block a user