Implement map api (ex. /v2/map/init-data)
Migrate registry.primary_groups to pydantic model
This commit is contained in:
parent
e43c88d0ab
commit
7e9e266157
6 changed files with 116 additions and 36 deletions
62
src/gisaf/api/map.py
Normal file
62
src/gisaf/api/map.py
Normal file
|
@ -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')
|
||||||
|
)
|
|
@ -29,6 +29,7 @@ from gisaf.models.to_migrate import (
|
||||||
)
|
)
|
||||||
from gisaf.live_utils import get_live_feature_info
|
from gisaf.live_utils import get_live_feature_info
|
||||||
from gisaf.api.dashboard import api as dashboard_api
|
from gisaf.api.dashboard import api as dashboard_api
|
||||||
|
from gisaf.api.map import api as map_api
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ api = FastAPI(
|
||||||
)
|
)
|
||||||
#api.add_middleware(SessionMiddleware, secret_key=conf.crypto.secret)
|
#api.add_middleware(SessionMiddleware, secret_key=conf.crypto.secret)
|
||||||
api.mount('/dashboard', dashboard_api)
|
api.mount('/dashboard', dashboard_api)
|
||||||
|
api.mount('/map', map_api)
|
||||||
|
|
||||||
|
|
||||||
@api.get('/bootstrap')
|
@api.get('/bootstrap')
|
||||||
|
|
|
@ -18,7 +18,7 @@ class CategoryGroup(BaseModel, table=True):
|
||||||
__tablename__ = 'category_group'
|
__tablename__ = 'category_group'
|
||||||
__table_args__ = gisaf_survey.table_args
|
__table_args__ = gisaf_survey.table_args
|
||||||
name: str | None = Field(sa_type=String(4), default=None, primary_key=True)
|
name: str | None = Field(sa_type=String(4), default=None, primary_key=True)
|
||||||
major: str
|
major: bool
|
||||||
long_name: str
|
long_name: str
|
||||||
categories: list['Category'] = Relationship(back_populates='category_group')
|
categories: list['Category'] = Relationship(back_populates='category_group')
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
from sqlmodel import Field, String, JSON, Relationship
|
from sqlmodel import Field, String, JSON, Relationship
|
||||||
|
|
||||||
from gisaf.models.models_base import Model
|
from gisaf.models.models_base import Model
|
||||||
|
from gisaf.models.category import CategoryGroup
|
||||||
from gisaf.models.metadata import gisaf_map
|
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
|
__table_args__ = gisaf_map.table_args
|
||||||
__tablename__ = 'map_base_style'
|
__tablename__ = 'map_base_style'
|
||||||
|
|
||||||
|
@ -25,7 +28,7 @@ class BaseStyle(Model):
|
||||||
return '<models.BaseStyle {self.name:s}>'.format(self=self)
|
return '<models.BaseStyle {self.name:s}>'.format(self=self)
|
||||||
|
|
||||||
|
|
||||||
class BaseMap(Model):
|
class BaseMap(Model, table=True):
|
||||||
__table_args__ = gisaf_map.table_args
|
__table_args__ = gisaf_map.table_args
|
||||||
__tablename__ = 'base_map'
|
__tablename__ = 'base_map'
|
||||||
|
|
||||||
|
@ -34,6 +37,7 @@ class BaseMap(Model):
|
||||||
|
|
||||||
id: int | None = Field(primary_key=True, default=None)
|
id: int | None = Field(primary_key=True, default=None)
|
||||||
name: str
|
name: str
|
||||||
|
layers: list['BaseMapLayer'] = Relationship(back_populates='base_map')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<models.BaseMap {self.name:s}>'.format(self=self)
|
return '<models.BaseMap {self.name:s}>'.format(self=self)
|
||||||
|
@ -41,8 +45,14 @@ class BaseMap(Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def selectinload(cls):
|
||||||
|
return [
|
||||||
|
cls.layers
|
||||||
|
]
|
||||||
|
|
||||||
class BaseMapLayer(Model):
|
|
||||||
|
class BaseMapLayer(Model, table=True):
|
||||||
__table_args__ = gisaf_map.table_args
|
__table_args__ = gisaf_map.table_args
|
||||||
__tablename__ = 'base_map_layer'
|
__tablename__ = 'base_map_layer'
|
||||||
|
|
||||||
|
@ -50,9 +60,9 @@ class BaseMapLayer(Model):
|
||||||
menu = 'Other'
|
menu = 'Other'
|
||||||
|
|
||||||
id: int | None = Field(primary_key=True, default=None)
|
id: int | None = Field(primary_key=True, default=None)
|
||||||
base_map_id: int = Field(foreign_key='base_map.id', index=True)
|
base_map_id: int = Field(foreign_key=gisaf_map.table('base_map.id'),
|
||||||
## Subclasses must include:
|
index=True)
|
||||||
# base_map: BaseMap = Relationship()
|
base_map: BaseMap = Relationship(back_populates='layers')
|
||||||
store: str = Field(sa_type=String(100))
|
store: str = Field(sa_type=String(100))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -61,14 +71,15 @@ class BaseMapLayer(Model):
|
||||||
cls.base_map
|
cls.base_map
|
||||||
]
|
]
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# def dyn_join_with(cls):
|
|
||||||
# return {
|
|
||||||
# 'base_map': BaseMap,
|
|
||||||
# }
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<models.BaseMapLayer {self.store or '':s}>"
|
return f"<models.BaseMapLayer {self.store or '':s}>"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.store or '':s}"
|
return f"{self.store or '':s}"
|
||||||
|
|
||||||
|
|
||||||
|
class MapInitData(BaseModel):
|
||||||
|
baseStyles: list[BaseStyle] = []
|
||||||
|
baseMaps: list[BaseMap] = []
|
||||||
|
groups: list[CategoryGroup] = []
|
||||||
|
stores: list[Store] = []
|
|
@ -9,7 +9,7 @@ class MapLibreStyle(BaseModel):
|
||||||
class Store(BaseModel):
|
class Store(BaseModel):
|
||||||
auto_import: bool
|
auto_import: bool
|
||||||
# base_gis_type: str
|
# base_gis_type: str
|
||||||
count: int
|
count: int | None = None
|
||||||
custom: bool
|
custom: bool
|
||||||
description: str
|
description: str
|
||||||
#extra: dict[str, Any] | None
|
#extra: dict[str, Any] | None
|
||||||
|
|
|
@ -77,6 +77,7 @@ class ModelRegistry:
|
||||||
"""
|
"""
|
||||||
stores: pd.DataFrame
|
stores: pd.DataFrame
|
||||||
categories: pd.DataFrame
|
categories: pd.DataFrame
|
||||||
|
primary_groups: list[CategoryGroup]
|
||||||
values: dict[str, PlottableModel]
|
values: dict[str, PlottableModel]
|
||||||
geom: dict[str, GeoModel]
|
geom: dict[str, GeoModel]
|
||||||
geom_live: dict[str, LiveGeoModel]
|
geom_live: dict[str, LiveGeoModel]
|
||||||
|
@ -567,31 +568,35 @@ class ModelRegistry:
|
||||||
self.stores['description'].fillna('', inplace=True)
|
self.stores['description'].fillna('', inplace=True)
|
||||||
|
|
||||||
## Layer groups: Misc, survey's primary groups, Live
|
## Layer groups: Misc, survey's primary groups, Live
|
||||||
self.primary_groups = await CategoryGroup.get_df()
|
async with db_session() as session:
|
||||||
self.primary_groups.sort_values('name', inplace=True)
|
query = select(CategoryGroup).order_by(CategoryGroup.name)
|
||||||
self.primary_groups['title'] = self.primary_groups['long_name']
|
data = await session.exec(query)
|
||||||
|
self.primary_groups = data.all()
|
||||||
|
|
||||||
## Add Misc and Live
|
## Add Misc and Live
|
||||||
self.primary_groups.loc['Misc'] = (
|
self.primary_groups.append(CategoryGroup(
|
||||||
False,
|
name='Misc',
|
||||||
'Misc and old layers (not coming from our survey; they will be organized, '
|
major=True,
|
||||||
'eventually as the surveys get more complete)',
|
long_name='Misc and old layers (not coming from our survey; '
|
||||||
'Misc',
|
'they will be organized, '
|
||||||
)
|
'eventually as the surveys get more complete)',
|
||||||
|
categories=[],
|
||||||
|
))
|
||||||
|
|
||||||
self.primary_groups.loc['Live'] = (
|
self.primary_groups.append(CategoryGroup(
|
||||||
False,
|
name='Live',
|
||||||
'Layers from data processing, sensors, etc, and are updated automatically',
|
major=True,
|
||||||
'Live',
|
long_name='Layers from data processing, sensors, etc, '
|
||||||
)
|
'and are updated automatically',
|
||||||
|
categories=[],
|
||||||
|
))
|
||||||
|
|
||||||
self.primary_groups.loc['Community'] = (
|
self.primary_groups.append(CategoryGroup(
|
||||||
False,
|
name='Community',
|
||||||
'Layers from community',
|
major=True,
|
||||||
'Community',
|
long_name='Layers from community',
|
||||||
)
|
categories=[],
|
||||||
|
))
|
||||||
self.primary_groups.sort_index(inplace=True)
|
|
||||||
|
|
||||||
#def make_group(group):
|
#def make_group(group):
|
||||||
# return GeomGroup(
|
# return GeomGroup(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue