Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1e9126a
backend recording-locations endpoint
BryonLewis Mar 25, 2026
8d6156c
fix model reference for migration
BryonLewis Mar 25, 2026
fa658be
initial map view
BryonLewis Mar 25, 2026
813125c
filtering map locations
BryonLewis Mar 25, 2026
5f6e858
Merge branch 'main' into location-view
BryonLewis Mar 25, 2026
323dcf3
map testing and adjustments
BryonLewis Mar 25, 2026
2f957c9
simplify bbox param
BryonLewis Mar 25, 2026
3583bdd
add a way to copy recordings with random grts cell id locations
BryonLewis Mar 26, 2026
068c45e
storing the map filter location
BryonLewis Mar 26, 2026
2e6e21c
formatting
BryonLewis Mar 26, 2026
b77da2d
Merge branch 'location-view' of https://github.com/Kitware/batai into…
BryonLewis Mar 26, 2026
4beeafd
linting, clearing filter bounds
BryonLewis Mar 26, 2026
a54a22d
add bbox visualization to map location
BryonLewis Mar 26, 2026
08122c2
unsubmitted neighbors bbox filtering
BryonLewis Mar 26, 2026
33443d0
BaseModel
BryonLewis Mar 26, 2026
9eec208
cleaning up queries
BryonLewis Mar 26, 2026
30a3392
use more raw geos functions instead of JSON conversion
BryonLewis Mar 27, 2026
25f825f
backend logic for recording location display
BryonLewis Apr 1, 2026
ea71c8a
Merge branch 'main' into location-view
BryonLewis Apr 1, 2026
e2855db
Merge branch 'location-backend' into location-view
BryonLewis Apr 1, 2026
649eacd
add bbox to neighbors schema
BryonLewis Apr 1, 2026
8bc2510
Merge branch 'location-backend' into location-view
BryonLewis Apr 1, 2026
8b3f0d4
linting
BryonLewis Apr 1, 2026
c4f7d40
Merge branch 'main' into location-view
BryonLewis Apr 1, 2026
c72a310
modify default single point size
BryonLewis Apr 1, 2026
fa0aff7
pinning maplibre due to a build issue
BryonLewis Apr 1, 2026
d033a85
Update client/src/use/useState.ts
BryonLewis Apr 2, 2026
04b3348
Update client/src/views/Recordings.vue
BryonLewis Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
467 changes: 343 additions & 124 deletions client/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"django-s3-file-field": "1.1.0",
"geojs": "1.19.0",
"lodash": "4.17.23",
"maplibre-gl": "5.17.0",
"vue": "3.5.30",
"vue-router": "5.0.4",
"vuetify": "3.12.3"
Expand All @@ -38,7 +39,7 @@
"eslint-plugin-vue": "10.8.0",
"sass-embedded": "^1.98.0",
"typescript": "5.9.3",
"vite": "8.0.2",
"vite": "8.0.3",
"vite-plugin-vuetify": "2.1.3",
"vue-tsc": "3.2.6"
}
Expand Down
49 changes: 49 additions & 0 deletions client/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios from "axios";
import { AxiosError } from "axios";
import { SpectroInfo } from "@components/geoJS/geoJSUtils";
import type { FeatureCollection, Point } from "geojson";

export interface Recording {
id: number;
Expand Down Expand Up @@ -31,6 +32,13 @@ export interface Recording {
tags_text?: string[];
}

export type RecordingLocationsFeatureProperties = {
recording_id: number;
filename: string;
};

export type RecordingLocationsGeoJson = FeatureCollection<Point, RecordingLocationsFeatureProperties>;

export interface Species {
species_code: string;
family: string;
Expand Down Expand Up @@ -287,6 +295,8 @@ export interface RecordingListParams {
sort_direction?: 'asc' | 'desc';
page?: number;
limit?: number;
/** WGS84 [minLon, minLat, maxLon, maxLat]; recordings must intersect this box. */
bbox?: [number, number, number, number];
}

/** Paginated recording list response (v-data-table-server compatible). */
Expand Down Expand Up @@ -315,6 +325,13 @@ async function getRecordings(getPublic = false, params?: RecordingListParams) {
if (params.sort_direction) query.set('sort_direction', params.sort_direction);
if (params.page !== undefined) query.set('page', String(params.page));
if (params.limit !== undefined) query.set('limit', String(params.limit));
if (
params.bbox !== undefined &&
params.bbox.length === 4 &&
params.bbox.every((n) => Number.isFinite(n))
) {
query.set('bbox', params.bbox.join(','));
}
}
if (!params?.page) query.set('page', '1');
if (!params?.limit) query.set('limit', '20');
Expand All @@ -329,6 +346,8 @@ export interface UnsubmittedNeighborsParams {
sort_direction?: 'asc' | 'desc';
/** Comma-separated or array of tag texts; recording must have all listed tags. */
tags?: string | string[];
/** Bounding box filter (lon/lat) as `[min_lon, min_lat, max_lon, max_lat]`. */
bbox?: [number, number, number, number];
}

export interface UnsubmittedNeighborsResponse {
Expand All @@ -347,6 +366,13 @@ async function getUnsubmittedNeighbors(
const tagStr = Array.isArray(params.tags) ? params.tags.join(',') : params.tags;
if (tagStr) query.set('tags', tagStr);
}
if (
params?.bbox !== undefined &&
params.bbox.length === 4 &&
params.bbox.every((n) => Number.isFinite(n))
) {
query.set('bbox', params.bbox.join(','));
}
const response = await axiosInstance.get<UnsubmittedNeighborsResponse>(
`/recording/unsubmitted-neighbors/?${query.toString()}`
);
Expand Down Expand Up @@ -455,6 +481,28 @@ async function getCellBbox(cellId: number) {
return await axiosInstance.get<GRTSCellBbox>(`/grts/${cellId}/bbox`);
}

export interface RecordingLocationsParams {
exclude_submitted?: boolean;
/** Comma-separated or array of tag texts; recording must have all listed tags. */
tags?: string | string[];
/** Bounding box filter (lon/lat) as `[min_lon, min_lat, max_lon, max_lat]`. */
bbox?: [number, number, number, number];
}

async function getRecordingLocations(params?: RecordingLocationsParams) {
const query = new URLSearchParams();
if (params?.exclude_submitted !== undefined) query.set("exclude_submitted", String(params.exclude_submitted));
if (params?.tags !== undefined) {
const tagStr = Array.isArray(params.tags) ? params.tags.join(",") : params.tags;
if (tagStr) query.set("tags", tagStr);
}
if (params?.bbox !== undefined && params.bbox.length === 4 && params.bbox.every((n) => Number.isFinite(n))) {
query.set("bbox", params.bbox.join(','));
}
const qs = query.toString();
return axiosInstance.get<RecordingLocationsGeoJson>(`/recording-locations/${qs ? `?${qs}` : ""}`);
}

async function getFileAnnotations(recordingId: number) {
return axiosInstance.get<FileAnnotation[]>(`recording/${recordingId}/recording-annotations`);
}
Expand Down Expand Up @@ -706,6 +754,7 @@ export {
getCellLocation,
getCellBbox,
getCellfromLocation,
getRecordingLocations,
getGuanoMetadata,
getFileAnnotations,
putFileAnnotation,
Expand Down
64 changes: 62 additions & 2 deletions client/src/components/MapLocation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export default defineComponent({
type: Object as PropType<{ x?: number; y?: number } | undefined>,
default: () => undefined,
},
bbox: {
type: Array as unknown as PropType<[number, number, number, number] | null>,
default: null,
},
grtsCellId: {
type: Number,
default: undefined,
Expand Down Expand Up @@ -63,8 +67,35 @@ export default defineComponent({
position: { bottom: 10, left: 10},
});

const drawBbox = (bounds: [number, number, number, number]) => {
const [west, south, east, north] = bounds;
const data = [
{ x: west, y: south },
{ x: east, y: south },
{ x: east, y: north },
{ x: west, y: north },
{ x: west, y: south },
];
bboxFeature.value.data([data]).style({
stroke: true,
strokeWidth: 2,
strokeColor: "black",
fill: true,
fillColor: "orange",
fillOpacity: 0.15,
}).draw();
bboxLayer.value.draw();
map.value.center({ x: (west + east) / 2, y: (south + north) / 2 });
map.value.zoom(6);
};

if (props.grtsCellId !== undefined) {
if (
props.bbox
&& props.bbox.length === 4
&& props.bbox.every((n) => Number.isFinite(n))
) {
drawBbox(props.bbox);
} else if (props.grtsCellId !== undefined) {
const annotation = await getCellBbox(props.grtsCellId);
const coordinates = annotation.data.geometry.coordinates;
const data = coordinates.map((point: number[]) => ({ x: point[0], y: point[1] }));
Expand Down Expand Up @@ -123,7 +154,36 @@ export default defineComponent({
}
});
watch(() => props.updateMap, () => {
if (props.location?.x && props.location?.y) {
if (
props.bbox
&& props.bbox.length === 4
&& props.bbox.every((n) => Number.isFinite(n))
&& bboxFeature.value
&& map.value
) {
const [west, south, east, north] = props.bbox;
const data = [
{ x: west, y: south },
{ x: east, y: south },
{ x: east, y: north },
{ x: west, y: north },
{ x: west, y: south },
];
bboxFeature.value
.data([data])
.style({
stroke: true,
strokeWidth: 2,
strokeColor: "black",
fill: true,
fillColor: "orange",
fillOpacity: 0.15,
})
.draw();
bboxLayer.value?.draw();
map.value.center({ x: (west + east) / 2, y: (south + north) / 2 });
map.value.zoom(6);
} else if (props.location?.x && props.location?.y) {
markerLocation.value = { x: props.location?.x, y: props.location.y };
markerFeature.value
.data([markerLocation.value])
Expand Down
Loading