feature-info: migrate to pydantic, fix live

This commit is contained in:
phil 2024-01-06 12:29:48 +05:30
parent 71cb491617
commit e3ed311390
5 changed files with 203 additions and 172 deletions

View file

@ -39,6 +39,7 @@ from gisaf.models.models_base import Model
from gisaf.models.metadata import gisaf_survey, gisaf_admin, survey
from gisaf.models.misc import Qml
from gisaf.models.category import Category
from gisaf.models.to_migrate import InfoItem
# from gisaf.models.survey import Equipment, Surveyor, Accuracy
# from gisaf.models.project import Project
@ -112,27 +113,35 @@ class BaseSurveyModel(BaseModel):
# info = await super(BaseSurveyModel, self).get_geo_info()
# return info
async def get_survey_info(self):
async def get_survey_info(self) -> list[InfoItem]:
info = await super(BaseSurveyModel, self).get_survey_info()
if self.category:
info['ISO layer name'] = self.iso_layer_name
info['survey category'] = '{} ({})'.format(self.category.description, self.category.name)
info.append(InfoItem(key='ISO layer name',
value=self.iso_layer_name))
info.append(InfoItem(key='survey category',
value=f'{self.category.description} ({self.category.name})'))
if self.project_id:
info['project'] = self.project.name
info.append(InfoItem(key='project',
value=self.project.name))
if self.srvyr_id:
info['surveyor'] = self.surveyor.name
info.append(InfoItem(key='surveyor',
value=self.surveyor.name))
if self.equip_id:
info['survey equipment'] = self.equipment.name
info.append(InfoItem(key='survey equipment',
value=self.equipment.name))
if self.accur_id:
info['survey accuracy'] = self.accuracy.name
info.append(InfoItem(key='survey accuracy',
value=self.accuracy.name))
if self.date:
info['survey date'] = self.date.strftime(LOCALE_DATE_FORMAT)
info.append(InfoItem(key='survey date',
value=self.date.strftime(LOCALE_DATE_FORMAT)))
if self.orig_id:
info['original id'] = self.orig_id
info.append(InfoItem(key='original id',
value=self.orig_id))
return info
@property
def iso_layer_name(self):
def iso_layer_name(self) -> str:
"""
The ISO layer name, built on the category and status
"""
@ -358,17 +367,17 @@ class GeoModelNoStatus(Model):
def __str__(self):
return self.caption
async def get_geo_info(self):
async def get_geo_info(self) -> list[InfoItem]:
"""
Geographical info
"""
return {}
return []
async def get_survey_info(self):
async def get_survey_info(self) -> list[InfoItem]:
"""
Quality info: project, source, accuracy...
"""
return OrderedDict()
return []
async def get_info(self) -> dict[str, str]:
"""
@ -420,13 +429,18 @@ class GeoModelNoStatus(Model):
async def get_properties(cls, df):
return {}
async def get_tags(self):
async def get_tags(self) -> list[InfoItem]:
from gisaf.models.tags import Tags
async with db_session() as session:
query = select(Tags.tags).where(Tags.store == self.get_store_name(),
Tags.ref_id == self.id)
tags = await session.exec(query)
return tags.one_or_none() or {}
data = await session.exec(query)
tags = data.one_or_none()
if tags is not None:
return [InfoItem(key=key, value=value)
for key, value in tags.items()]
else:
return []
@cached_property
def shapely_geom(self):
@ -453,7 +467,8 @@ class GeoModelNoStatus(Model):
"""
return ''
async def get_feature_as_dict(self, simplify_tolerance=None, reproject=False, css_class_prefix=''):
async def get_feature_as_dict(self, simplify_tolerance=None,
reproject=False, css_class_prefix=''):
"""
Get the parameters of this object (feature)
:param css_class_prefix: for leaflet only
@ -694,7 +709,8 @@ class GeoModelNoStatus(Model):
@classmethod
async def get_geo_df(cls, where=None, crs=None, reproject=False,
filter_columns=False, with_popup=False, **kwargs) -> gpd.GeoDataFrame:
filter_columns=False, with_popup=False,
**kwargs) -> gpd.GeoDataFrame:
"""
Return a GeoPandas GeoDataFrame of all records
:param where: where clause for the query (eg. Model.attr=='foo')
@ -827,38 +843,42 @@ class GeoPointModelNoStatus(GeoModelNoStatus):
return writer
async def get_geo_info(self):
info = OrderedDict()
if self.shapely_geom:
info['longitude'] = '{:.6f}'.format(self.shapely_geom.x)
info['latitude'] = '{:.6f}'.format(self.shapely_geom.y)
return info
async def get_geo_info(self) -> list[InfoItem]:
return [
InfoItem(key='longitude', value='{:.6f}.format(self.geom.x)'),
InfoItem(key='latitude', value='{:.6f}.format(self.geom.y)'),
]
class GeoPointModel(GeoPointModelNoStatus, GeoModel):
...
class GeoPointZModel(GeoPointModel):
geom: Annotated[str, WKBElement] = Field(sa_type=Geometry('POINTZ', dimension=3, srid=conf.geo.srid))
geom: Annotated[str, WKBElement] = Field(
sa_type=Geometry('POINTZ', dimension=3, srid=conf.geo.srid))
shapefile_model: ClassVar[int] = POINTZ
def get_coords(self):
return (self.shapely_geom.x, self.shapely_geom.y, self.shapely_geom.z)
async def get_geo_info(self):
async def get_geo_info(self) -> list[InfoItem]:
info = await super(GeoPointZModel, self).get_geo_info()
info['elevation (m)'] = '{:.2f}'.format(self.shapely_geom.z)
info.append(
InfoItem(key='elevation (m)', value='{:.2f}'.format(self.shapely_geom.z))
)
return info
class GeoPointMModel(GeoPointZModel):
shapefile_model: ClassVar[int] = POINTZ
geom: Annotated[str, WKBElement] = Field(sa_type=Geometry('POINTZ', dimension=3, srid=conf.geo.srid))
geom: Annotated[str, WKBElement] = Field(
sa_type=Geometry('POINTZ', dimension=3, srid=conf.geo.srid))
class GeoLineModel(GeoModel):
shapefile_model: ClassVar[int] = POLYLINE
geom: Annotated[str, WKBElement] = Field(sa_type=Geometry('LINESTRING', srid=conf.geo.srid))
geom: Annotated[str, WKBElement] = Field(
sa_type=Geometry('LINESTRING', srid=conf.geo.srid))
mapbox_type: ClassVar[str] = 'line'
base_gis_type: ClassVar[str] = 'Line'
@ -910,34 +930,38 @@ class GeoLineModel(GeoModel):
points = wkb.loads(self.geom.data)
return zip(points.coords.xy[0], points.coords.xy[1])
async def get_geo_info(self):
info = OrderedDict()
async def get_geo_info(self) -> list[InfoItem]:
bounds = self.shapely_geom.bounds
info['longitude'] = '{:.6f} - {:.6f}'.format(bounds[0], bounds[2])
info['latitude'] = '{:.6f} - {:.6f}'.format(bounds[1], bounds[3])
info['length (m)'] = '{self.length:.2f}'.format(self=self)
return info
return [
InfoItem(key='longitude', value='{:.6f} - {:.6f}'.format(bounds[0], bounds[2])),
InfoItem(key='latitude', value='{:.6f} - {:.6f}'.format(bounds[1], bounds[3])),
InfoItem(key='length (m)', value='{self.length:.2f}'.format(self=self))
]
class GeoLineModelZ(GeoLineModel):
shapefile_model: ClassVar[int] = POLYLINEZ
geom: Annotated[str, WKBElement] = Field(sa_type=Geometry('LINESTRINGZ', dimension=3, srid=conf.geo.srid))
geom: Annotated[str, WKBElement] = Field(
sa_type=Geometry('LINESTRINGZ', dimension=3, srid=conf.geo.srid))
async def get_geo_info(self):
async def get_geo_info(self) -> list[InfoItem]:
info = await super(GeoLineModelZ, self).get_geo_info()
elevations = [cc[2] for cc in self.shapely_geom.coords]
elev_min = min(elevations)
elev_max = max(elevations)
if elev_min == elev_max:
info['elevation (m)'] = '{:.2f}'.format(elev_min)
info.append(InfoItem(key='elevation (m)',
value='{:.2f}'.format(elev_min)))
else:
info['elevation (m)'] = '{:.2f} - {:.2f}'.format(elev_min, elev_max)
info.append(InfoItem(key='elevation (m)',
value='{:.2f} - {:.2f}'.format(elev_min, elev_max)))
return info
class GeoPolygonModel(GeoModel):
shapefile_model: ClassVar[int] = POLYGON
geom: Annotated[str, WKBElement] = Field(sa_type=Geometry('POLYGON', srid=conf.geo.srid))
geom: Annotated[str, WKBElement] = Field(
sa_type=Geometry('POLYGON', srid=conf.geo.srid))
mapbox_type: ClassVar[str] = 'fill'
base_gis_type: ClassVar[str] = 'Polygon'
@ -993,24 +1017,31 @@ class GeoPolygonModel(GeoModel):
points = wkb.loads(self.geom.data)
return zip(points.exterior.coords.xy[0], points.exterior.coords.xy[1])
async def get_geo_info(self):
info = OrderedDict()
async def get_geo_info(self) -> list[InfoItem]:
info = []
area = self.area
bounds = self.shapely_geom.bounds
info['longitude'] = '{:.6f} - {:.6f}'.format(bounds[0], bounds[2])
info['latitude'] = '{:.6f} - {:.6f}'.format(bounds[1], bounds[3])
info['length (m)'] = '{:.2f}'.format(self.length)
info['area (sq. m)'] = '{:.1f} sq. m'.format(area)
info['area (ha)'] = '{:.1f} ha'.format(area / 10000)
info['area (acre)'] = '{:.1f} acres'.format(area / 4046.85643005078874)
info.append(InfoItem(key='longitude',
value='{:.6f} - {:.6f}'.format(bounds[0], bounds[2])))
info.append(InfoItem(key='latitude',
value='{:.6f} - {:.6f}'.format(bounds[1], bounds[3])))
info.append(InfoItem(key='length (m)',
value='{:.2f}'.format(self.length)))
info.append(InfoItem(key='area (sq. m)',
value='{:.1f} sq. m'.format(area)))
info.append(InfoItem(key='area (ha)',
value='{:.1f} ha'.format(area / 10000)))
info.append(InfoItem(key='area (acre)',
value='{:.1f} acres'.format(area / 4046.85643005078874)))
return info
class GeoPolygonModelZ(GeoPolygonModel):
shapefile_model: ClassVar[int] = POLYGONZ
geom: Annotated[str, WKBElement] = Field(sa_type=Geometry('POLYGONZ', dimension=3, srid=conf.geo.srid))
geom: Annotated[str, WKBElement] = Field(
sa_type=Geometry('POLYGONZ', dimension=3, srid=conf.geo.srid))
async def get_geo_info(self):
async def get_geo_info(self) -> list[InfoItem]:
info = await super(GeoPolygonModelZ, self).get_geo_info()
if hasattr(self.shapely_geom, 'exterior'):
coords = self.shapely_geom.exterior.coords
@ -1020,9 +1051,11 @@ class GeoPolygonModelZ(GeoPolygonModel):
elev_min = min(elevations)
elev_max = max(elevations)
if elev_min == elev_max:
info['elevation (m)'] = '{:.2f}'.format(elev_min)
info.append(InfoItem(key='elevation (m)',
value='{:.2f}'.format(elev_min)))
else:
info['elevation (m)'] = '{:.2f} - {:.2f}'.format(elev_min, elev_max)
info.append(InfoItem(key='elevation (m)',
value='{:.2f} - {:.2f}'.format(elev_min, elev_max)))
return info
@ -1038,7 +1071,8 @@ class LineWorkSurveyModel(SurveyModel):
def match_raw_points(self):
reprojected_geom = transform(reproject_func, self.shapely_geom)
reprojected_geom_geoalchemy = from_shape(reprojected_geom, conf.raw_survey_srid)
raw_survey_points_project = self.raw_model.query.filter(self.raw_model.project_id==self.project_id)
raw_survey_points_project = self.raw_model.query.filter(
self.raw_model.project_id==self.project_id)
query = raw_survey_points_project.filter(
func.ST_Distance(reprojected_geom_geoalchemy, self.raw_model.geom) < conf.epsilon
)

View file

@ -28,7 +28,7 @@ class DataProvider(BaseModel):
class InfoItem(BaseModel):
key: str
value: str
value: str | float | int
class InfoCategory(BaseModel):