From 7e9e2661579964068b335e691fef3246b835bc87 Mon Sep 17 00:00:00 2001 From: phil Date: Thu, 11 Jan 2024 12:55:42 +0530 Subject: [PATCH] Implement map api (ex. /v2/map/init-data) Migrate registry.primary_groups to pydantic model --- src/gisaf/api/map.py | 62 +++++++++++++++++++++++++++++++++++ src/gisaf/api/v2.py | 2 ++ src/gisaf/models/category.py | 2 +- src/gisaf/models/map_bases.py | 37 +++++++++++++-------- src/gisaf/models/store.py | 2 +- src/gisaf/registry.py | 47 ++++++++++++++------------ 6 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 src/gisaf/api/map.py diff --git a/src/gisaf/api/map.py b/src/gisaf/api/map.py new file mode 100644 index 0000000..c2ea7a9 --- /dev/null +++ b/src/gisaf/api/map.py @@ -0,0 +1,62 @@ +from collections import OrderedDict, defaultdict +import logging +from pathlib import Path +from fastapi import Depends, FastAPI, HTTPException, status, responses +from sqlalchemy.orm import selectinload +from sqlmodel import select + +from gisaf.config import conf +from gisaf.models.map_bases import BaseMap, BaseMapLayer, BaseStyle, MapInitData +from gisaf.registry import registry +from gisaf.database import db_session +from gisaf.models.authentication import User + +logger = logging.getLogger(__name__) + +api = FastAPI( + default_response_class=responses.ORJSONResponse, +) + +async def get_base_styles(): + async with db_session() as session: + query = select(BaseStyle.name)\ + .where(BaseStyle.enabled==True)\ + .order_by(BaseStyle.id) + data = await session.exec(query) + base_styles = data.all() + ## TODO: tiles_registry + logger.warning('TODO: tiles_registry') + # base_styles.extend(tiles_registry.mbtiles.values()) + return [BaseStyle(name=bs) for bs in base_styles] + +async def get_base_maps() -> list[BaseMap]: + async with db_session() as session: + query1 = select(BaseMap).options(selectinload(BaseMap.layers)) + data1 = await session.exec(query1) + base_maps = data1.all() + return base_maps + base_map_dict = {bm.id: bm.name for bm in base_maps} + query2 = select(BaseMapLayer).options(selectinload(BaseMapLayer.base_map)) + data2 = await session.exec(query2) + base_map_layers = data2.all() + bms = defaultdict(list) + for bml in base_map_layers: + breakpoint() + if bml.store: + bms[base_map_dict[bml.base_map_id]].append(name=bml.store) + return [ + BaseMap(name=bm, stores=bmls) + for bm, bmls in OrderedDict(sorted(bms.items())).items() + ] + +@api.get('/init-data') +async def get_init_data() -> MapInitData: + await registry.update_stores_counts() + df = registry.stores.reset_index().\ + drop(columns=['model', 'raw_model', 'base_gis_type']) + return MapInitData( + baseStyles=await get_base_styles(), + baseMaps=await get_base_maps(), + groups=registry.primary_groups, + stores=df.to_dict(orient='records') + ) diff --git a/src/gisaf/api/v2.py b/src/gisaf/api/v2.py index c051a24..dc20681 100644 --- a/src/gisaf/api/v2.py +++ b/src/gisaf/api/v2.py @@ -29,6 +29,7 @@ from gisaf.models.to_migrate import ( ) from gisaf.live_utils import get_live_feature_info from gisaf.api.dashboard import api as dashboard_api +from gisaf.api.map import api as map_api logger = logging.getLogger(__name__) @@ -38,6 +39,7 @@ api = FastAPI( ) #api.add_middleware(SessionMiddleware, secret_key=conf.crypto.secret) api.mount('/dashboard', dashboard_api) +api.mount('/map', map_api) @api.get('/bootstrap') diff --git a/src/gisaf/models/category.py b/src/gisaf/models/category.py index 5fa7a76..0e6c311 100644 --- a/src/gisaf/models/category.py +++ b/src/gisaf/models/category.py @@ -18,7 +18,7 @@ class CategoryGroup(BaseModel, table=True): __tablename__ = 'category_group' __table_args__ = gisaf_survey.table_args name: str | None = Field(sa_type=String(4), default=None, primary_key=True) - major: str + major: bool long_name: str categories: list['Category'] = Relationship(back_populates='category_group') diff --git a/src/gisaf/models/map_bases.py b/src/gisaf/models/map_bases.py index cee33c6..3c3e760 100644 --- a/src/gisaf/models/map_bases.py +++ b/src/gisaf/models/map_bases.py @@ -1,12 +1,15 @@ from typing import Any +from pydantic import BaseModel from sqlmodel import Field, String, JSON, Relationship from gisaf.models.models_base import Model +from gisaf.models.category import CategoryGroup from gisaf.models.metadata import gisaf_map +from gisaf.models.store import Store -class BaseStyle(Model): +class BaseStyle(Model, table=True): __table_args__ = gisaf_map.table_args __tablename__ = 'map_base_style' @@ -25,7 +28,7 @@ class BaseStyle(Model): return ''.format(self=self) -class BaseMap(Model): +class BaseMap(Model, table=True): __table_args__ = gisaf_map.table_args __tablename__ = 'base_map' @@ -34,6 +37,7 @@ class BaseMap(Model): id: int | None = Field(primary_key=True, default=None) name: str + layers: list['BaseMapLayer'] = Relationship(back_populates='base_map') def __repr__(self): return ''.format(self=self) @@ -41,8 +45,14 @@ class BaseMap(Model): def __str__(self): return self.name + @classmethod + def selectinload(cls): + return [ + cls.layers + ] -class BaseMapLayer(Model): + +class BaseMapLayer(Model, table=True): __table_args__ = gisaf_map.table_args __tablename__ = 'base_map_layer' @@ -50,9 +60,9 @@ class BaseMapLayer(Model): menu = 'Other' id: int | None = Field(primary_key=True, default=None) - base_map_id: int = Field(foreign_key='base_map.id', index=True) - ## Subclasses must include: - # base_map: BaseMap = Relationship() + base_map_id: int = Field(foreign_key=gisaf_map.table('base_map.id'), + index=True) + base_map: BaseMap = Relationship(back_populates='layers') store: str = Field(sa_type=String(100)) @classmethod @@ -61,14 +71,15 @@ class BaseMapLayer(Model): cls.base_map ] - # @classmethod - # def dyn_join_with(cls): - # return { - # 'base_map': BaseMap, - # } - def __repr__(self): return f"" def __str__(self): - return f"{self.store or '':s}" \ No newline at end of file + return f"{self.store or '':s}" + + +class MapInitData(BaseModel): + baseStyles: list[BaseStyle] = [] + baseMaps: list[BaseMap] = [] + groups: list[CategoryGroup] = [] + stores: list[Store] = [] \ No newline at end of file diff --git a/src/gisaf/models/store.py b/src/gisaf/models/store.py index 9718116..4760bb3 100644 --- a/src/gisaf/models/store.py +++ b/src/gisaf/models/store.py @@ -9,7 +9,7 @@ class MapLibreStyle(BaseModel): class Store(BaseModel): auto_import: bool # base_gis_type: str - count: int + count: int | None = None custom: bool description: str #extra: dict[str, Any] | None diff --git a/src/gisaf/registry.py b/src/gisaf/registry.py index f7ae665..000b105 100644 --- a/src/gisaf/registry.py +++ b/src/gisaf/registry.py @@ -77,6 +77,7 @@ class ModelRegistry: """ stores: pd.DataFrame categories: pd.DataFrame + primary_groups: list[CategoryGroup] values: dict[str, PlottableModel] geom: dict[str, GeoModel] geom_live: dict[str, LiveGeoModel] @@ -567,31 +568,35 @@ class ModelRegistry: self.stores['description'].fillna('', inplace=True) ## Layer groups: Misc, survey's primary groups, Live - self.primary_groups = await CategoryGroup.get_df() - self.primary_groups.sort_values('name', inplace=True) - self.primary_groups['title'] = self.primary_groups['long_name'] + async with db_session() as session: + query = select(CategoryGroup).order_by(CategoryGroup.name) + data = await session.exec(query) + self.primary_groups = data.all() ## Add Misc and Live - self.primary_groups.loc['Misc'] = ( - False, - 'Misc and old layers (not coming from our survey; they will be organized, ' - 'eventually as the surveys get more complete)', - 'Misc', - ) + self.primary_groups.append(CategoryGroup( + name='Misc', + major=True, + long_name='Misc and old layers (not coming from our survey; ' + 'they will be organized, ' + 'eventually as the surveys get more complete)', + categories=[], + )) - self.primary_groups.loc['Live'] = ( - False, - 'Layers from data processing, sensors, etc, and are updated automatically', - 'Live', - ) + self.primary_groups.append(CategoryGroup( + name='Live', + major=True, + long_name='Layers from data processing, sensors, etc, ' + 'and are updated automatically', + categories=[], + )) - self.primary_groups.loc['Community'] = ( - False, - 'Layers from community', - 'Community', - ) - - self.primary_groups.sort_index(inplace=True) + self.primary_groups.append(CategoryGroup( + name='Community', + major=True, + long_name='Layers from community', + categories=[], + )) #def make_group(group): # return GeomGroup(