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

Merging latest HydroProcessDB changes #35

Merged
merged 23 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aa102e5
Added citation url to the map details
MSDrao Nov 5, 2024
194f968
Added footer related changes
MSDrao Nov 6, 2024
b222ce3
Merge pull request #25 from CUAHSI/add-citation-url-to-map-details
MSDrao Nov 6, 2024
3cda42e
Updated the header and footer css
MSDrao Nov 7, 2024
9dbf32e
Merge pull request #26 from CUAHSI/add-citation-url-to-map-details
MSDrao Nov 7, 2024
3cd22b7
Change N ro None in droplist
MSDrao Nov 15, 2024
beb630d
Updated taxonomies dropdown to treeview
MSDrao Nov 15, 2024
0c80b40
Fix for model count update issue on filtering
MSDrao Nov 17, 2024
53ad453
formatted the file
MSDrao Nov 19, 2024
e14bc77
Updated the about page
MSDrao Dec 3, 2024
6c95b6e
Merge pull request #29 from CUAHSI/taxonomies-treeview
Castronova Dec 3, 2024
d48ddc8
Merge pull request #27 from CUAHSI/fix/model-count-update
Castronova Dec 3, 2024
07bc71f
Merge pull request #28 from CUAHSI/replace-n-with-none-in-droplist
Castronova Dec 3, 2024
15d58ec
Updated footer
MSDrao Dec 3, 2024
48c1aa7
Merge pull request #32 from CUAHSI/feature/footer-update-v3
devincowan Dec 4, 2024
f0a67eb
Updated teammember image locations
MSDrao Dec 4, 2024
3bcde96
Updated the about page
MSDrao Dec 5, 2024
ca9f7c1
Merge pull request #30 from CUAHSI/feature/about-page-rebuilt
Castronova Dec 5, 2024
4d5367d
Updated the map detail popup content
MSDrao Dec 5, 2024
18df58e
Hidden unwanted options in UI
MSDrao Dec 5, 2024
c172a16
Updated the map content based on open access condition
MSDrao Dec 5, 2024
19ef135
Merge pull request #34 from CUAHSI/non-open-access-check
Castronova Dec 5, 2024
043a6a1
Merge pull request #33 from CUAHSI/remove-unwanted-options
Castronova Dec 5, 2024
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
8 changes: 7 additions & 1 deletion api/hydroprocess_db/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

from geoalchemy2 import Geometry, WKBElement, shape
from geojson_pydantic import Feature, FeatureCollection, Point
from pydantic import ConfigDict, model_serializer
from pydantic import ConfigDict, model_serializer, BaseModel
from pydantic_extra_types.coordinate import Latitude, Longitude
from shapely import to_geojson
from sqlmodel import Column, Field, Relationship, SQLModel
from typing import List, Optional


class Citation(SQLModel, table=True):
Expand Down Expand Up @@ -227,3 +228,8 @@ class ProcessAltName(SQLModel, table=True):
process_id: int | None = Field(default=None)

process_taxonomy: ProcessTaxonomy | None = Relationship(back_populates="process_alt_name")

class ModelCountRequest(BaseModel):
spatialzone_ids: Optional[List[int]] = None
temporalzone_ids: Optional[List[int]] = None
process_taxonomy_ids: Optional[List[int]] = None
38 changes: 23 additions & 15 deletions api/hydroprocess_db/app/routers/statistics/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,39 @@
from sqlmodel import select

from app.db import get_session
from app.models import ModelType, PerceptualModel
from app.models import ModelType, PerceptualModel, ModelCountRequest, ProcessTaxonomy

router = APIRouter()


@router.get(
@router.post(
"/model_type_count",
description="Get the count of models for each model type.",
response_model=dict[str, int],
)
def get_model_count_by_type(*, session=Depends(get_session)):
"""
Get the count of models for each model type.

Parameters:
- session: The async session to use for database operations.

Returns:
- A dictionary with the count of models for each model type.
"""
def get_model_count_by_type(
request: ModelCountRequest,
session=Depends(get_session)
):
model_types = session.exec(select(ModelType)).all()
model_type_count = {}

for model_type in model_types:
matching_models = session.query(PerceptualModel).where(PerceptualModel.model_type_id == model_type.id)
model_type_count[model_type.name] = matching_models.count()
query = session.query(PerceptualModel).where(PerceptualModel.model_type_id == model_type.id)

if request.spatialzone_ids:
query = query.where(PerceptualModel.spatialzone_id.in_(request.spatialzone_ids))

if request.temporalzone_ids:
query = query.where(PerceptualModel.temporalzone_id.in_(request.temporalzone_ids))

if request.process_taxonomy_ids:
query = query.join(PerceptualModel.process_taxonomies).where(
ProcessTaxonomy.id.in_(request.process_taxonomy_ids)
)

matching_models = query.all()
model_type_count[model_type.name] = len(matching_models)

return model_type_count


Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"vite-plugin-vuetify": "^1.0.2",
"vue": "^3.3.4",
"vue-router": "^4.2.4",
"vuetify": "^3.3.21"
"vuetify": "^3.7.4"
},
"devDependencies": {
"@mdi/font": "^7.3.67",
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ const paths = [
attrs: { to: "/" },
label: "Map",
},
{
attrs: { to: "/api" },
label: "API",
},
// {
// attrs: { to: "/api" },
// label: "API",
// },
{
attrs: { to: "/about" },
label: "About",
Expand Down
Binary file added frontend/src/assets/hilary.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/src/assets/nsf_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/src/assets/ryoko.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/src/assets/sdsu_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 16 additions & 2 deletions frontend/src/components/DataViewDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,29 @@ let querying = ref(true)
let modelTypeCounts = ref({})
let totalModels = ref(0)

const query = async () => {
const query = async (filters = {}) => {
querying.value = true
const response = await fetch(ENDPOINTS.model_type_count)
const response = await fetch(ENDPOINTS.model_type_count, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(filters)
});
const counts = await response.json()

// Delete the 'Figure model (Hand-drawn)' key
delete counts['Figure model (Hand-drawn)']

modelTypeCounts.value = counts
totalModels.value = Object.values(counts).reduce((acc, count) => acc + count, 0)
querying.value = false
}

defineExpose({
query
})

query()

</script>
Expand Down
121 changes: 115 additions & 6 deletions frontend/src/components/FilterDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,37 @@
<v-sheet class="mx-auto" elevation="8">
<h3 class="text-h6 ma-2 text-center">Model Filters</h3>
<v-divider></v-divider>
<v-autocomplete v-model="selectedProcesses" :items="process_taxonomies" item-title="process" item-value="id"
<!-- <v-autocomplete v-model="selectedProcesses" :items="process_taxonomies" item-title="process" item-value="id"
label="Process Taxonomies" @update:modelValue="filter" clearable chips multiple
:loading="filtering"></v-autocomplete>
:loading="filtering"></v-autocomplete> -->
<v-text-field
v-model="searchTreeText"
label="Search Process Taxonomies"
:clear-icon="mdiCloseCircleOutline"
clearable
dark
flat
hide-details
solo-inverted>
</v-text-field>
<v-treeview
v-model:selected="selectedTreeItems"
:items="treeViewData"
select-strategy="clasic"
item-value="id"
selectable
:search="searchTreeText"
activatable
@update:modelValue="updateMap"
>
<template v-slot:prepend="{ item, isOpen }">
<v-icon>
{{ isOpen ? mdiFolderOpen : mdiFolder }}
</v-icon>
</template>
</v-treeview>


<v-autocomplete v-model="selectedSpatialZones" :items="spatialZones" item-title="spatial_property" item-value="id"
label="Spatial Zones" @update:modelValue="filter" clearable chips multiple :loading="filtering"></v-autocomplete>
<v-autocomplete v-model="selectedTemporalZones" :items="temporalZones" item-title="temporal_property"
Expand All @@ -15,7 +43,7 @@
<v-card-text>
<v-btn-toggle v-model="textSearchFields" @update:modelValue="filter" class="mb-2" multiple outlined
variant="text" divided>
<v-btn value="long_name">Name</v-btn>
<v-btn value="long_name">Title</v-btn>
<v-btn value="citation">Citation</v-btn>
<v-btn value="textmodel_snipped">Abstract</v-btn>
</v-btn-toggle>
Expand All @@ -31,11 +59,14 @@
import { ref, computed, nextTick } from 'vue'
import { usePerceptualModelStore } from "@/stores/perceptual_models";
import { useMapStore } from '@/stores/map';
import { mdiFolderOpen, mdiFolder, mdiCloseCircleOutline } from '@mdi/js';


const perceptualModelStore = usePerceptualModelStore();
const mapStore = useMapStore()

defineEmits(['selectModel', 'toggle'])
const emit = defineEmits(['selectModel', 'toggle', 'onFilter'])


let modelFeatures = ref({})
const filtering = ref()
Expand All @@ -53,22 +84,90 @@ const temporalZones = ref([])
const selectedTemporalZones = ref([])
const searchTerm = ref(null)
const textSearchFields = ref([])
const treeViewData = ref([])
const selectedTreeItems = ref([])
const searchTreeText = ref('')

const hasTextSearchFields = computed(() => {
return textSearchFields.value.length > 0
})

// Fetch the process taxonomies, spatial zones, and temporal zones
perceptualModelStore.fetchProcessTaxonomies().then((pt) => {
process_taxonomies.value = pt
process_taxonomies.value = pt;
treeViewData.value = buildTree(pt);
})

function buildTree(data) {
const root = {};

// Helper function to insert item into the correct place in the tree
const insert = (path, item) => {
let current = root;
path.forEach((part, index) => {
// Check if part already exists as a child, if not create it
if (!current[part]) {
current[part] = {
title: part,
id: item.id,
children: {}
};
}
// If it's the last part, assign the item values to the node
if (index === path.length - 1) {
current[part] = {
id: item.id,
title: item.process,
children: current[part].children || {}
};
}
current = current[part].children;
});
};

// Insert each item in data into the tree
data.forEach(item => {
const path = item.identifier.split(".");
insert(path, item);
});

// Convert tree object with nested children into desired array format
const convertToArray = (node) => {
return Object.values(node).map(child => {
const childrenArray = convertToArray(child.children);
const nodeObject = {
id: child.id,
title: child.title
};
if (childrenArray.length > 0) {
nodeObject.children = childrenArray;
}
return nodeObject;
});
};

return convertToArray(root);
}

perceptualModelStore.fetchSpatialZones().then((sz) => {
replaceNwithNone(sz, 'spatial_property');
spatialZones.value = sz
})
perceptualModelStore.fetchTemporalZones().then((tz) => {
replaceNwithNone(tz, 'temporal_property');
temporalZones.value = tz
})

const replaceNwithNone = (items, propName) => {
for(let item of items){
if(item[propName] === 'N') {
item[propName] = "None";
break;
}
}
return items;
}

const checkSearchTerm = (searchTerm, fieldsToSearch, feature) => {
if (!searchTerm) {
return true
Expand All @@ -83,6 +182,8 @@ const checkSearchTerm = (searchTerm, fieldsToSearch, feature) => {


async function filter() {
emit('onFilter', {selectedSpatialZones, selectedTemporalZones, selectedProcesses})

filtering.value = true
await nextTick()
// reset search term if no text search fields are selected
Expand All @@ -94,12 +195,20 @@ async function filter() {
const spatial = selectedSpatialZones.value.length == 0 || selectedSpatialZones.value.includes(feature.properties.spatialzone_id)
const temporal = selectedTemporalZones.value.length == 0 || selectedTemporalZones.value.includes(feature.properties.temporalzone_id)
const search = checkSearchTerm(searchTerm.value, textSearchFields.value, feature)

return process && spatial && temporal && search
}
mapStore.filterFeatures(filterFunction)
filtering.value = false
}

const updateMap = async () => {
selectedProcesses.value = [];
selectedTreeItems.value.forEach((item) => {
selectedProcesses.value.push(item);
});
await nextTick();
filter();
}
</script>

<style scoped>
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/components/TheAppBar.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<v-app-bar v-if="!$route.meta.hideNavigation" color="navbar" ref="appBar" id="app-bar" elevate-on-scroll fixed app>
<div class="d-flex align-end full-height pa-2 align-center w-100">
<div class="d-flex align-end full-height pa-2 align-center w-100 position-relative">
<v-app-bar-title>Perceptual Models Around the World<div class="text-subtitle-1">McMillan Hydrology Lab</div>
</v-app-bar-title>

Expand All @@ -16,16 +16,16 @@
</nav>
</v-card>
<v-spacer></v-spacer>
<UserLogin @logged-in="login" v-if="!mdAndDown" :mobile="false" />
<!-- <UserLogin @logged-in="login" v-if="!mdAndDown" :mobile="false" /> -->

<v-app-bar-nav-icon @click="$emit('toggleMobileNav')" v-else />
<!-- <v-app-bar-nav-icon @click="$emit('toggleMobileNav')" v-else /> -->
</div>
</v-app-bar>
</template>
<script setup>
import { RouterLink } from 'vue-router'
// import { RouterLink } from 'vue-router'
import { useDisplay } from 'vuetify'
import UserLogin from "@/components/UserLogin.vue";
// import UserLogin from "@/components/UserLogin.vue";
import { useAuthStore } from '../stores/auth';
defineProps(['paths'])
defineEmits(['toggleMobileNav'])
Expand All @@ -51,6 +51,8 @@ function login(){
.nav-items {
border-radius: 2rem !important;
overflow: hidden;
position: absolute;
left: 43%;

&>a.v-btn:first-child {
border-top-left-radius: 2rem !important;
Expand Down
Loading
Loading