Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Database Schemas #20

Merged
merged 7 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
184 changes: 102 additions & 82 deletions src/backend/app/db/db_models.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
from datetime import datetime
from typing import cast
from sqlalchemy import (
BigInteger,
Column,
String,
Integer,
ForeignKey,
ForeignKeyConstraint,
DateTime,
SmallInteger,
Boolean,
Float,
Enum,
Index,
desc,
ARRAY,
LargeBinary,
)
from sqlalchemy.dialects.postgresql import UUID

from app.db.database import Base
from geoalchemy2 import Geometry, WKBElement
from app.models.enums import (
TaskStatus,
TaskAction,
TaskSplitType,
ProjectStatus,
ProjectVisibility,
MappingLevel,
ProjectPriority,
UserRole,
State,
)
from sqlalchemy.orm import (
object_session,
Expand Down Expand Up @@ -62,95 +61,38 @@ class DbOrganisation(Base):
url = cast(str, Column(String))


class DbTaskHistory(Base):
"""Describes the history associated with a task."""

__tablename__ = "task_history"

id = cast(int, Column(Integer, primary_key=True))
project_id = cast(int, Column(Integer, ForeignKey("projects.id"), index=True))
task_id = cast(int, Column(Integer, nullable=False))
action = cast(TaskAction, Column(Enum(TaskAction), nullable=False))
action_text = cast(str, Column(String))
action_date = cast(datetime, Column(DateTime, nullable=False, default=timestamp))
user_id = cast(
int,
Column(
BigInteger,
ForeignKey("users.id", name="fk_users"),
index=True,
nullable=False,
),
)

# Define relationships
user = relationship(DbUser, uselist=False, backref="task_history_user")
actioned_by = relationship(DbUser, overlaps="task_history_user,user")

__table_args__ = (
ForeignKeyConstraint(
[task_id, project_id], ["tasks.id", "tasks.project_id"], name="fk_tasks"
),
Index("idx_task_history_composite", "task_id", "project_id"),
Index("idx_task_history_project_id_user_id", "user_id", "project_id"),
{},
)


class DbTask(Base):
"""Describes an individual mapping Task."""

__tablename__ = "tasks"

# Table has composite PK on (id and project_id)
id = cast(int, Column(Integer, primary_key=True, autoincrement=True))
id = cast(str, Column(UUID(as_uuid=True), primary_key=True))
project_id = cast(
int, Column(Integer, ForeignKey("projects.id"), index=True, primary_key=True)
str, Column(UUID(as_uuid=True), ForeignKey("projects.id"), nullable=False)
)
project_task_index = cast(int, Column(Integer))
project_task_name = cast(str, Column(String))
outline = cast(WKBElement, Column(Geometry("POLYGON", srid=4326)))
task_status = cast(TaskStatus, Column(Enum(TaskStatus), default=TaskStatus.READY))
locked_by = cast(
int,
Column(BigInteger, ForeignKey("users.id", name="fk_users_locked"), index=True),
)
mapped_by = cast(
int,
Column(BigInteger, ForeignKey("users.id", name="fk_users_mapper"), index=True),
)
validated_by = cast(
int,
Column(
BigInteger, ForeignKey("users.id", name="fk_users_validator"), index=True
),
)

# Define relationships
task_history = relationship(
DbTaskHistory, cascade="all", order_by=desc(DbTaskHistory.action_date)
)
lock_holder = relationship(DbUser, foreign_keys=[locked_by])
mapper = relationship(DbUser, foreign_keys=[mapped_by])


class DbProject(Base):
"""Describes a Mapping Project."""

__tablename__ = "projects"

id = cast(int, Column(Integer, primary_key=True))
id = cast(str, Column(UUID(as_uuid=True), primary_key=True))
name = cast(str, Column(String))
short_description = cast(str, Column(String))
description = cast(str, Column(String))
per_task_instructions = cast(str, Column(String))
location_str = cast(str, Column(String))
created = cast(datetime, Column(DateTime, default=timestamp, nullable=False))
last_updated = cast(datetime, Column(DateTime, default=timestamp))

# GEOMETRY
outline = cast(WKBElement, Column(Geometry("POLYGON", srid=4326)))
centroid = cast(WKBElement, Column(Geometry("POINT", srid=4326)))
no_fly_zones = cast(WKBElement, Column(Geometry("POLYGON", srid=4326)))

organisation_id = cast(
int,
Expand All @@ -162,11 +104,26 @@ class DbProject(Base):
)
organisation = relationship(DbOrganisation, backref="projects")

# flight params
overlap_percent = cast(float, Column(Float, nullable=True))
gsd_cm_px = cast(float, Column(Float, nullable=True)) # in cm_px
camera_bearings = cast(list[int], Column(ARRAY(SmallInteger), nullable=True))
gimble_angles_degrees = cast(
list, Column(ARRAY(SmallInteger), nullable=True)
) # degrees
is_terrain_follow = cast(bool, Column(Boolean, default=False))
dem_url = cast(str, Column(String, nullable=False))
hashtags = cast(list, Column(ARRAY(String))) # Project hashtag

output_orthophoto_url = cast(str, Column(String, nullable=True))
output_pointcloud_url = cast(str, Column(String, nullable=True))
output_raw_url = cast(str, Column(String, nullable=True))

# PROJECT CREATION
author_id = cast(
int,
str,
Column(
BigInteger,
String,
ForeignKey("users.id", name="fk_users"),
nullable=False,
),
Expand All @@ -185,19 +142,6 @@ class DbProject(Base):
),
)

mapper_level = cast(
MappingLevel,
Column(
Enum(MappingLevel),
default=MappingLevel.INTERMEDIATE,
nullable=False,
index=True,
),
)

priority = cast(
ProjectPriority, Column(Enum(ProjectPriority), default=ProjectPriority.MEDIUM)
)
task_split_type = cast(TaskSplitType, Column(Enum(TaskSplitType), nullable=True))
task_split_dimension = cast(int, Column(SmallInteger, nullable=True))

Expand Down Expand Up @@ -244,3 +188,79 @@ def tasks_bad(self):
.with_parent(self)
.count()
)


class TaskEvent(Base):
__tablename__ = "task_events"

event_id = cast(str, Column(UUID(as_uuid=True), primary_key=True))
project_id = cast(
str, Column(UUID(as_uuid=True), ForeignKey("projects.id"), nullable=False)
)
task_id = cast(
str, Column(UUID(as_uuid=True), ForeignKey("tasks.id"), nullable=False)
)
user_id = cast(str, Column(String(100), ForeignKey("users.id"), nullable=False))
comment = cast(str, Column(String))

state = cast(State, Column(Enum(TaskStatus), nullable=False))
created_at = cast(datetime, Column(DateTime, default=timestamp))

__table_args__ = (
Index("idx_task_event_composite", "task_id", "project_id"),
Index("idx_task_event_project_id_user_id", "user_id", "project_id"),
)


class Drone(Base):
__tablename__ = "drones"

id = cast(int, Column(Integer, primary_key=True, autoincrement=True))
model = cast(str, Column(String, unique=True, nullable=False))
manufacturer = cast(str, Column(String))
camera_model = cast(str, Column(String))
sensor_width = cast(float, Column(Float, nullable=True))
sensor_height = cast(float, Column(Float, nullable=True))
max_battery_health = cast(int, Column(Integer, nullable=True))
focal_length = cast(float, Column(Float, nullable=True))
image_width = cast(int, Column(Integer, nullable=True))
image_height = cast(int, Column(Integer, nullable=True))
max_altitude = cast(int, Column(Integer, nullable=True))
max_speed = cast(float, Column(Float, nullable=True))
weight = cast(int, Column(Integer, nullable=True))
max_battery_health = cast(int, Column(Integer, nullable=True))
created = cast(datetime, Column(DateTime, default=timestamp, nullable=False))


class DroneFlight(Base):
__tablename__ = "drone_flights"

flight_id = cast(str, Column(UUID(as_uuid=True), primary_key=True))
drone_id = cast(int, Column(Integer, ForeignKey("drones.id"), nullable=False))
task_id = cast(
str, Column(UUID(as_uuid=True), ForeignKey("tasks.id"), nullable=False)
)
user_id = cast(str, Column(String(100), ForeignKey("users.id"), nullable=False))
flight_start = cast(datetime, Column(DateTime))
flight_end = cast(datetime, Column(DateTime))
user_estimated_battery_time_minutes = cast(int, Column(SmallInteger))
override_camera_bearings = cast(list, Column(ARRAY(SmallInteger)))
override_gimble_angles_degrees = cast(int, Column(ARRAY(SmallInteger)))
override_height_from_ground_meters = cast(int, Column(SmallInteger))
override_image_overlap_percent = cast(int, Column(SmallInteger))
waypoint_file = cast(bytes, Column(LargeBinary))
created_at = cast(datetime, Column(DateTime, default=timestamp))


class GroundControlPoint(Base):
__tablename__ = "ground_control_points"

id = cast(str, Column(UUID(as_uuid=True), primary_key=True))
project_id = cast(
str, Column(UUID(as_uuid=True), ForeignKey("projects.id"), nullable=False)
)
image_relative_path = cast(str, Column(String))
pixel_x = cast(int, Column(SmallInteger))
pixel_y = cast(int, Column(SmallInteger))
reference_point = cast(WKBElement, Column(Geometry("POLYGON", srid=4326)))
created_at = cast(datetime, Column(DateTime, default=timestamp))
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""add overlap in projects table

Revision ID: 1ebfe5e4cf1c
Revises: 4b1534031978
Create Date: 2024-07-03 08:13:33.354101

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = "1ebfe5e4cf1c"
down_revision: Union[str, None] = "4b1534031978"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("projects", sa.Column("overlap", sa.Float(), nullable=True))
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("projects", "overlap")
# ### end Alembic commands ###
69 changes: 69 additions & 0 deletions src/backend/app/migrations/versions/4b1534031978_add_gcp_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Add GCP table

Revision ID: 4b1534031978
Revises: d2b9d45d3ede
Create Date: 2024-07-03 05:10:56.759505

"""
from typing import Sequence, Union
import geoalchemy2
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = "4b1534031978"
down_revision: Union[str, None] = "d2b9d45d3ede"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"ground_control_points",
sa.Column("id", sa.UUID(), nullable=False),
sa.Column("project_id", sa.UUID(), nullable=False),
sa.Column("image_relative_path", sa.String(), nullable=True),
sa.Column("pixel_x", sa.SmallInteger(), nullable=True),
sa.Column("pixel_y", sa.SmallInteger(), nullable=True),
sa.Column(
"reference_point",
geoalchemy2.types.Geometry(
geometry_type="POLYGON",
srid=4326,
from_text="ST_GeomFromEWKT",
name="geometry",
),
nullable=True,
),
sa.Column("created_at", sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(
["project_id"],
["projects.id"],
),
sa.PrimaryKeyConstraint("id"),
)
# op.create_index('idx_ground_control_points_reference_point', 'ground_control_points', ['reference_point'], unique=False, postgresql_using='gist')
op.drop_column("drone_flights", "imagery_data_url")
op.add_column("projects", sa.Column("output_raw_url", sa.String(), nullable=True))
op.add_column("task_events", sa.Column("comment", sa.String(), nullable=True))
op.drop_column("task_events", "detail")
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"task_events",
sa.Column("detail", sa.VARCHAR(), autoincrement=False, nullable=True),
)
op.drop_column("task_events", "comment")
op.drop_column("projects", "output_raw_url")
op.add_column(
"drone_flights",
sa.Column("imagery_data_url", sa.VARCHAR(), autoincrement=False, nullable=True),
)
# op.drop_index('idx_ground_control_points_reference_point', table_name='ground_control_points', postgresql_using='gist')
op.drop_table("ground_control_points")
# ### end Alembic commands ###
Loading
Loading