Skip to content

Commit

Permalink
Merge branch 'main' into temporal-annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
BryonLewis committed Feb 7, 2024
2 parents e690218 + ce43eb8 commit 6333167
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 18 deletions.
20 changes: 20 additions & 0 deletions bats_ai/core/migrations/0006_alter_recording_recording_location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.1.13 on 2024-02-02 16:19

import django.contrib.gis.db.models.fields
from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
('core', '0005_recording_public'),
]

operations = [
migrations.AlterField(
model_name='recording',
name='recording_location',
field=django.contrib.gis.db.models.fields.GeometryField(
blank=True, null=True, srid=4326
),
),
]
2 changes: 1 addition & 1 deletion bats_ai/core/models/recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Recording(TimeStampedModel, models.Model):
recorded_date = models.DateField(blank=True, null=True)
equipment = models.TextField(blank=True, null=True)
comments = models.TextField(blank=True, null=True)
recording_location = models.GeometryField(srid=0, blank=True, null=True)
recording_location = models.GeometryField(srid=4326, blank=True, null=True)
grts_cell_id = models.IntegerField(blank=True, null=True)
grts_cell = models.IntegerField(blank=True, null=True)
public = models.BooleanField(default=False)
Expand Down
22 changes: 18 additions & 4 deletions bats_ai/core/views/recording.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from datetime import datetime
import json
import logging

from django.contrib.auth.models import User
from django.contrib.gis.geos import Point
from django.core.files.storage import default_storage
from django.http import HttpRequest
from ninja import File, Form, Schema
Expand Down Expand Up @@ -33,8 +35,11 @@ class RecordingSchema(Schema):
class RecordingUploadSchema(Schema):
name: str
recorded_date: str
equipment: str = None
comments: str = None
equipment: str | None
comments: str | None
latitude: float = None
longitude: float = None
gridCellId: int = None
publicVal: bool = None


Expand Down Expand Up @@ -80,17 +85,21 @@ def create_recording(
publicVal: bool = False,
):
converted_date = datetime.strptime(payload.recorded_date, '%Y-%m-%d')
point = None
if payload.latitude and payload.longitude:
point = Point(payload.longitude, payload.latitude)
recording = Recording(
name=payload.name,
owner_id=request.user.pk,
audio_file=audio_file,
recorded_date=converted_date,
equipment=payload.equipment,
grts_cell_id=payload.gridCellId,
recording_location=point,
public=publicVal,
comments=payload.comments,
)
recording.save()

return {'message': 'Recording updated successfully', 'id': recording.pk}


Expand All @@ -112,6 +121,9 @@ def update_recording(request: HttpRequest, id: int, recording_data: RecordingUpl
recording.recorded_date = converted_date
if recording_data.publicVal is not None and recording_data.publicVal != recording.public:
recording.public = recording_data.publicVal
if recording_data.latitude and recording_data.longitude:
point = Point(recording_data.longitude, recording_data.latitude)
recording.recording_location = point

recording.save()

Expand All @@ -126,10 +138,13 @@ def get_recordings(request: HttpRequest, public: bool | None = None):
else:
recordings = Recording.objects.filter(owner=request.user).values()

# TODO with larger dataset it may be better to do this in a queryset instead of python
for recording in recordings:
user = User.objects.get(id=recording['owner_id'])
recording['owner_username'] = user.username
recording['audio_file_presigned_url'] = default_storage.url(recording['audio_file'])
if recording['recording_location']:
recording['recording_location'] = json.loads(recording['recording_location'].json)
unique_users_with_annotations = (
Annotations.objects.filter(recording_id=recording['id'])
.values('owner')
Expand All @@ -142,7 +157,6 @@ def get_recordings(request: HttpRequest, public: bool | None = None):
).exists()
recording['userMadeAnnotations'] = user_has_annotations

# Return the serialized data
return list(recordings)


Expand Down
26 changes: 21 additions & 5 deletions client/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface Recording {
recorded_date: string;
equipment?: string,
comments?: string;
recording_location?: null | [number, number],
recording_location?: null | GeoJSON.Point,
grts_cell_id?: null | number;
grts_cell?: null | number;
public: boolean;
Expand Down Expand Up @@ -94,20 +94,29 @@ interface PaginatedNinjaResponse<T> {
items: T[],
}


export type UploadLocation = null | { latitude?: number, longitude?: number, gridCellId?: number};

export const axiosInstance = axios.create({
baseURL: import.meta.env.VUE_APP_API_ROOT as string,
});


async function uploadRecordingFile(file: File, name: string, recorded_date: string, equipment: string, comments: string, publicVal = false ) {
async function uploadRecordingFile(file: File, name: string, recorded_date: string, equipment: string, comments: string, publicVal = false, location: UploadLocation = null ) {
const formData = new FormData();
formData.append('audio_file', file);
formData.append('name', name);
formData.append('recorded_date', recorded_date);
formData.append('equipment', equipment);
formData.append('comments', comments);
if (location) {
if (location.latitude && location.longitude) {
formData.append('latitude', location.latitude.toString());
formData.append('longitude', location.longitude.toString());
}
if (location.gridCellId) {
formData.append('gridCellId', location.gridCellId.toString());
}
}

const recordingParams = {
name,
Expand All @@ -126,14 +135,21 @@ async function uploadRecordingFile(file: File, name: string, recorded_date: stri
});
}

async function patchRecording(recordingId: number, name: string, recorded_date: string, equipment: string, comments: string, publicVal = false ) {
async function patchRecording(recordingId: number, name: string, recorded_date: string, equipment: string, comments: string, publicVal = false, location: UploadLocation = null ) {
const latitude = location ? location.latitude : undefined;
const longitude = location ? location.longitude : undefined;
const gridCellId = location ? location.gridCellId : undefined;

await axiosInstance.patch(`/recording/${recordingId}`,
{
name,
recorded_date,
equipment,
comments,
publicVal
publicVal,
latitude,
longitude,
gridCellId
},
{
headers: {
Expand Down
124 changes: 124 additions & 0 deletions client/src/components/MapLocation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<!-- eslint-disable @typescript-eslint/no-explicit-any -->
<script lang="ts">
import { defineComponent, PropType, Ref, ref } from "vue";
import useState from "../use/useState";
import { watch } from "vue";
import geo, { GeoEvent } from "geojs";

export default defineComponent({
name: "MapLocation",
components: {},
props: {
editor: {
type: Boolean,
default: true,
},
size: {
type: Object as PropType<{ width: number; height: number }>,
default: () => ({ width: 300, height: 300 }),
},
location: {
type: Object as PropType<{ x?: number; y?: number } | undefined>,
default: () => undefined,
},
updateMap: {
type: Number,
default: 0,
},
},
emits: ['location'],
setup(props, { emit }) {
const usCenter = { x: -90.5855, y: 39.8333 };
const mapRef: Ref<HTMLDivElement | null> = ref(null);
const map: Ref<any> = ref();
const mapLayer: Ref<any> = ref();
const markerLayer: Ref<any> = ref();
const markerFeature: Ref<any> = ref();
const markerLocation: Ref<{ x: number; y: number } | null> = ref(null);
watch(mapRef, () => {
if (mapRef.value) {
const centerPoint = props.location && props.location.x && props.location.y ? props.location : usCenter;
const zoomLevel = props.location && props.location.x && props.location.y ? 6 : 3;
map.value = geo.map({ node: mapRef.value, center: centerPoint, zoom: zoomLevel });
mapLayer.value = map.value.createLayer("osm");
markerLayer.value = map.value.createLayer("feature", { features: ["marker"] });
markerFeature.value = markerLayer.value.createFeature("marker");
if (props.location?.x && props.location?.y) {
markerLocation.value = { x: props.location?.x, y: props.location.y };
markerFeature.value
.data([markerLocation.value])
.style({
symbol: geo.markerFeature.symbols.drop,
symbolValue: 1 / 3,
rotation: -Math.PI / 2,
radius: 30,
strokeWidth: 5,
strokeColor: "blue",
fillColor: "yellow",
rotateWithMap: false,
})
.draw();
}
if (props.editor) {
mapLayer.value.geoOn(geo.event.mouseclick, (e: GeoEvent) => {
// Place a marker at the point
const { x, y } = e.geo;
markerLocation.value = { x, y };
markerFeature.value
.data([markerLocation.value])
.style({
symbol: geo.markerFeature.symbols.drop,
symbolValue: 1 / 3,
rotation: -Math.PI / 2,
radius: 30,
strokeWidth: 5,
strokeColor: "blue",
fillColor: "yellow",
rotateWithMap: false,
})
.draw();
emit('location', { lon: x, lat:y });

});
}
}
});
watch(() => props.updateMap, () => {
if (props.location?.x && props.location?.y && markerLocation.value) {
markerLocation.value = { x: props.location?.x, y: props.location.y };
markerFeature.value
.data([markerLocation.value])
.style({
symbol: geo.markerFeature.symbols.drop,
symbolValue: 1 / 3,
rotation: -Math.PI / 2,
radius: 30,
strokeWidth: 5,
strokeColor: "blue",
fillColor: "yellow",
rotateWithMap: false,
})
.draw();
const centerPoint = props.location && props.location.x && props.location.y ? props.location : usCenter;
const zoomLevel = props.location && props.location.x && props.location.y ? 6 : 3;
if (map.value) {
map.value.zoom(zoomLevel);
map.value.center(centerPoint);
}

}
});
return {
mapRef,
};
},
});
</script>

<template>
<v-card class="pa-0 ma-0">
<div ref="mapRef" :style="`width:${size.width}px; height:${size.height}px`" />
</v-card>
</template>

<style lang="scss" scoped></style>
Loading

0 comments on commit 6333167

Please sign in to comment.