Coverage for toardb/stationmeta/schemas.py: 92%
547 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-03 20:32 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-03 20:32 +0000
1# SPDX-FileCopyrightText: 2021 Forschungszentrum Jülich GmbH
2# SPDX-License-Identifier: MIT
4"""
5Pydantic schemas for TOAR database
7"""
9from typing import List, Dict
10from pydantic import BaseModel, Field, BaseConfig, Json, validator
11from geoalchemy2 import WKTElement
12from geoalchemy2.shape import to_shape
13import datetime as dt
14from toardb.contacts.schemas import Contact
15import toardb
16from toardb.utils.utils import provenance
19# the following class was taken from:
20# https://github.com/tiangolo/fastapi/issues/312
21class Coordinates(BaseModel):
22 lat: float = Field(..., ge=-90, le=90, description="latitude coordinate of station (decimal degrees_north). This is our best estimate of the station location which is not always identical to the official station coordinates (see potential changelog entry).")
23 lng: float = Field(..., ge=-180, le=180, description="longitude coordinate of station (decimal degrees_east). This is our best estimate of the station location which is not always identical to the official station coordinates (see potential changelog entry).")
24 alt: float = Field(..., description="altitude of station (in m above sea level). This is our best estimate of the station altitude, which is not always identical to the reported station altitude, but frequently uses the elevation from google earth instead (see potential changelog entry).")
26# ======== StationmetaCore =========
28class StationmetaCoreBase(BaseModel):
29 id: int = Field(None, description="for internal use only")
30 codes: List[str] = Field(None, description="list of station's codes")
31 name: str = Field(None, description="Station name for use in TOAR; normally equal to data provider’s name")
32 coordinates: Coordinates = Field(None, description="Best estimate of station latitude, longitude and altitude")
33 coordinate_validation_status: str = Field(None, description="Flag indicating whether the location of a station has been verified (see controlled vocabulary: Coordinate Validity)")
34 country: str = Field(None, description="The country, where the station resides, or which operates the station (e.g. in Antarctica) (see controlled vocabulary: Country Code)")
35 state: str = Field(None, description="The state or province, where the station resides")
36 type: str = Field(None, description="Type of station (see controlled vocabulary: Station Type)")
37 type_of_area: str = Field(None, description="Type of station area (see controlled vocabulary: Station Type Of Area)")
38 timezone: str = Field(None, description="Station timezone (see controlled vocabulary: Timezone)")
39 additional_metadata: Json = Field(None, description="Any additional metadata about the station as a JSON data structure")
41 class Config(BaseConfig):
42 orm_mode = True
44 @validator('coordinate_validation_status')
45 def check_coordinate_validation_status(cls, v):
46 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.CV_vocabulary))[0].display_str
48 @validator('country')
49 def check_country(cls, v):
50 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.CN_vocabulary))[0].display_str
52 @validator('type')
53 def check_type(cls, v):
54 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.ST_vocabulary))[0].display_str
56 @validator('type_of_area')
57 def check_type_of_area(cls, v):
58 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TA_vocabulary))[0].display_str
60 @validator('timezone')
61 def check_timezone(cls, v):
62 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TZ_vocabulary))[0].display_str
65class StationmetaCorePatch(BaseModel):
66 codes: List[str] = None
67 name: str = None
68 coordinates: Coordinates = None
69 coordinate_validation_status: str = None
70 country: str = None
71 state: str = None
72 type: str = None
73 type_of_area: str = None
74 timezone: str = None
75 additional_metadata: Json = None
77 class Config(BaseConfig):
78 orm_mode = True
81class StationmetaCoreCreate(StationmetaCoreBase):
82 pass
84 @validator('coordinate_validation_status')
85 def check_coordinate_validation_status(cls, v):
86 if tuple(filter(lambda x: x.string == v, toardb.toardb.CV_vocabulary)):
87 return v
88 else:
89 raise ValueError(f"coordinate validation status not known: {v}")
91 @validator('country')
92 def check_country(cls, v):
93 if tuple(filter(lambda x: x.string == v, toardb.toardb.CN_vocabulary)):
94 return v
95 else:
96 raise ValueError(f"country not known: {v}")
98 @validator('type')
99 def check_type(cls, v):
100 if tuple(filter(lambda x: x.string == v, toardb.toardb.ST_vocabulary)):
101 return v
102 else:
103 raise ValueError(f"type of environment not known: {v}")
105 @validator('type_of_area')
106 def check_type_of_area(cls, v):
107 if tuple(filter(lambda x: x.string == v, toardb.toardb.TA_vocabulary)):
108 return v
109 else:
110 raise ValueError(f"type of area not known: {v}")
112 @validator('timezone')
113 def check_timezone(cls, v):
114 if tuple(filter(lambda x: x.string == v, toardb.toardb.TZ_vocabulary)):
115 return v
116 else:
117 raise ValueError(f"timezone not known: {v}")
120class StationmetaCore(StationmetaCoreBase):
121 id: int = Field(..., description="for internal use only")
123 class Config:
124 orm_mode = True
127def get_geom_from_coordinates(coordinates: Coordinates) -> str:
128 geom_wkte = f'SRID=4326;POINTZ({coordinates.lng} {coordinates.lat} {coordinates.alt})'
129 return geom_wkte
132def get_coordinates_from_geom(geom: WKTElement) -> Coordinates:
133 shply_geom = to_shape(geom)
134 coordinates = Coordinates(lng=shply_geom.x, lat=shply_geom.y, alt=shply_geom.z)
135 return coordinates
137def get_coordinates_from_string(point: str) -> Coordinates:
138 coordinates = Coordinates(**dict(zip(['lng','lat','alt'],point[9:-1].split())))
139 return coordinates
141# ======== StationmetaAnnotation =========
143class StationmetaAnnotationBase(BaseModel):
144 id: int = None
145 kind: str = Field(..., description="kind of annotation (see controlled vocabulary: Kind Of Annotation)")
146 text: str = Field(..., description="text of annotation")
147 date_added: dt.datetime = Field(..., description="timestamp when annotation was added")
148 approved: bool = Field(..., description="Flag indicating whether the annotation of a station has been verified")
149 contributor_id: int = Field(..., description="ID of contributor who added the annotation")
151 @validator('kind')
152 def check_kind(cls, v):
153 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.AK_vocabulary))[0].display_str
156class StationmetaAnnotationPatch(BaseModel):
157 kind: int = None
158 text: str = None
159 date_added: dt.datetime = None
160 approved: bool = None
161 contributor_id: int = None
163 @validator('kind')
164 def check_kind(cls, v):
165 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.AK_vocabulary))[0].display_str
168class StationmetaAnnotationCreate(StationmetaAnnotationBase):
170 @validator('kind')
171 def check_kind(cls, v):
172 if tuple(filter(lambda x: x.string == v, toardb.toardb.AK_vocabulary)):
173 return v
174 else:
175 raise ValueError(f"kind of annotation code not known: {v}")
179class StationmetaAnnotation(StationmetaAnnotationBase):
180 id: int = Field(..., description="for internal use only")
182 class Config:
183 orm_mode = True
185# ======== StationmetaAux =========
187# -------- StationmetaAuxDoc ---------
189class StationmetaAuxDocBase(BaseModel):
190 id: int = None
191 resource_description: str = Field(..., description="Description of the resource")
192 date_added: dt.datetime = Field(..., description="Date of metadata record entry into TOAR database")
193 resource: str = Field(..., description="auxillary document file")
194 station_id: int = Field(..., description="internal station_id to which this resource belongs")
197class StationmetaAuxDocPatch(BaseModel):
198 resource_description: str = None
199 date_added: dt.datetime = None
200 resource: str = None
201 station_id: int = None
204class StationmetaAuxDocCreate(StationmetaAuxDocBase):
205 pass
208class StationmetaAuxDoc(StationmetaAuxDocBase):
209 id: int = Field(..., description="for internal use only")
211 class Config:
212 orm_mode = True
214# -------- StationmetaAuxImage ---------
216class StationmetaAuxImageBase(BaseModel):
217 id: int = None
218 resource_description: str = Field(..., description="Description of the resource")
219 date_added: dt.datetime = Field(..., description="Date of metadata record entry into TOAR database")
220 resource: str = Field(..., description="auxillary image file")
221 image_height: int = Field(..., description="Number of image pixels in direction of height")
222 image_width: int = Field(..., description="Number of image pixels in direction of width")
223 station_id: int = Field(..., description="internal station_id to which this resource belongs")
226class StationmetaAuxImagePatch(BaseModel):
227 resource_description: str = None
228 date_added: dt.datetime = None
229 resource: str = None
230 image_height: int = None
231 image_width: int = None
232 station_id: int = None
235class StationmetaAuxImageCreate(StationmetaAuxImageBase):
236 pass
239class StationmetaAuxImage(StationmetaAuxImageBase):
240 id: int = Field(..., description="for internal use only")
242 class Config:
243 orm_mode = True
245# -------- StationmetaAuxUrl ---------
247class StationmetaAuxUrlBase(BaseModel):
248 id: int = None
249 resource_description: str = Field(..., description="Description of the resource")
250 date_added: dt.datetime = Field(..., description="Date of metadata record entry into TOAR database")
251 resource: str = Field(..., description="URL")
252 station_id: int = Field(..., description="internal station_id to which this resource belongs")
255class StationmetaAuxUrlPatch(BaseModel):
256 resource_description: str = None
257 date_added: dt.datetime = None
258 resource: str = None
259 station_id: int = None
262class StationmetaAuxUrlCreate(StationmetaAuxUrlBase):
263 pass
266class StationmetaAuxUrl(StationmetaAuxUrlBase):
267 id: int = Field(..., description="for internal use only")
269 class Config:
270 orm_mode = True
272# ======== StationmetaGlobal =========
274class StationmetaGlobalBase(BaseModel):
275 mean_topography_srtm_alt_90m_year1994: float = Field(..., description="mean value within a radius of 90 m around station location of the following data of the year 1994: " + str(provenance['topography_srtm']))
276 mean_topography_srtm_alt_1km_year1994: float = Field(..., description="mean value within a radius of 1 km around station location of the following data of the year 1994: " + str(provenance['topography_srtm']))
277 max_topography_srtm_relative_alt_5km_year1994: float = Field(..., description="maximum value within a radius of 5 km around station location with relative altitude of the following data of the year 1994: " + str(provenance['topography_srtm']))
278 min_topography_srtm_relative_alt_5km_year1994: float = Field(..., description="minimum value within a radius of 5 km around station location with relative altitude of the following data of the year 1994: " + str(provenance['topography_srtm']))
279 stddev_topography_srtm_relative_alt_5km_year1994: float = Field(..., description="standard deviation within a radius of 5 km around station location with relative altitude of the following data of the year 1994: " + str(provenance['topography_srtm']))
280 climatic_zone_year2016: str = Field(..., description= "value for the year 2016 of the following data: " + str(provenance['climatic_zone']) + " (see controlled vocabulary: Climatic Zone 2019)")
281 htap_region_tier1_year2010: str = Field(..., description= "value for the year 2010 of the following data: " + str(provenance['htap_region_tier1']) + " (see controlled vocabulary: Station HTAP Region)")
282 dominant_landcover_year2012: str = Field(..., description="value for the year 2012 of the following data: " + str(provenance['landcover']) + " (see controlled vocabulary: Station Landcover Type)")
283 landcover_description_25km_year2012: str = Field(..., description="description of the values for the year 2012 within a radius of 25 km around station location of the following data: " + str(provenance['landcover']) + " (see controlled vocabulary: Station Landcover Type)")
284 dominant_ecoregion_year2017: str = Field(..., description="value for the year 2017 of the following data: " + str(provenance['ecoregion']) + " (see controlled vocabulary: Station ECO Region Type)")
285 ecoregion_description_25km_year2017: str = Field(..., description="description of the values for the year 2017 within a radius of 25 km around station location of the following data: " + str(provenance['ecoregion']) + " (see controlled vocabulary: Station ECO Region Type)")
286 distance_to_major_road_year2020: float = Field(..., description="value for the year 2020 of the following data: " + str(provenance['major_road']))
287 mean_stable_nightlights_1km_year2013: float = Field(..., description="mean value within a radius of 1 km around station location of the following data of the year 2013: " + str(provenance['stable_nightlights']))
288 mean_stable_nightlights_5km_year2013: float = Field(..., description="mean value within a radius of 5 km around station location of the following data of the year 2013: " + str(provenance['stable_nightlights']))
289 max_stable_nightlights_25km_year2013: float = Field(..., description="maximum value within a radius of 5 km around station location of the following data of the year 2013: " + str(provenance['stable_nightlights']))
290 max_stable_nightlights_25km_year1992: float = Field(..., description="maximum value within a radius of 25 km around station location of the following data of the year 2013: " + str(provenance['stable_nightlights']))
291 mean_population_density_250m_year2015: float = Field(..., description="mean value within a radius of 250 m around station location of the following data of the year 2015: " + str(provenance['population_density']))
292 mean_population_density_5km_year2015: float = Field(..., description="mean value within a radius of 5 km around station location of the following data of the year 2015: " + str(provenance['population_density']))
293 max_population_density_25km_year2015: float = Field(..., description="maximum value within a radius of 25 km around station location of the following data of the year 2015: " + str(provenance['population_density']))
294 mean_population_density_250m_year1990: float = Field(..., description="human population on a square of 250 m for the year 1990 (residents km-2)")
295 mean_population_density_5km_year1990: float = Field(..., description="mean value within a radius of 250 m around station location of the following data of the year 1990: " + str(provenance['population_density']))
296 max_population_density_25km_year1990: float = Field(..., description="maximum value within a radius of 25 km around station location of the following data of the year 1990: " + str(provenance['population_density']))
297 mean_nox_emissions_10km_year2015: float = Field(..., description="mean value within a radius of 10 km around station location of the following data of the year 2015: " + str(provenance['nox_emissions']))
298 mean_nox_emissions_10km_year2000: float = Field(..., description="mean value within a radius of 10 km around station location of the following data of the year 2000: " + str(provenance['nox_emissions']))
299 toar1_category: str = Field(..., description="The station classification for the Tropsopheric Ozone Assessment Report based on the station proxy data that are stored in the TOAR database (see controlled vocabulary: Station TOAR Category)")
300 toar2_category: str = Field(..., description="The station classification for the TOAR-II based on the station proxy data that are stored in the TOAR database and obtained from an ML approach (see controlled vocabulary: Station TOAR Category)")
301# station_id: int = Field(..., description="internal station_id to which these global data belong")
304 @validator('climatic_zone_year2016')
305 def check_climatic_zone(cls, v):
306 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.CZ_vocabulary))[0].display_str
308 @validator('toar1_category')
309 def check_toar1_category(cls, v):
310 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TC_vocabulary))[0].display_str
312 @validator('toar2_category')
313 def check_toar2_category(cls, v):
314 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TA_vocabulary))[0].display_str
316 @validator('htap_region_tier1_year2010')
317 def check_htap_region_tier1(cls, v):
318 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TR_vocabulary))[0].display_str
320 @validator('dominant_landcover_year2012')
321 def check_dominant_landcover_year2012(cls, v):
322 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.LC_vocabulary))[0].display_str
324 @validator('landcover_description_25km_year2012')
325 def check_landcover_description_25km_year2012(cls, v):
326 return get_full_description_from_abbreviation(toardb.toardb.LC_vocabulary, v)
328 @validator('dominant_ecoregion_year2017')
329 def check_dominant_ecoregion_year2017(cls, v):
330 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.ER_vocabulary))[0].display_str
332 @validator('ecoregion_description_25km_year2017')
333 def check_ecoregion_description_25km_year2017(cls, v):
334 return get_full_description_from_abbreviation(toardb.toardb.ER_vocabulary, v)
337class StationmetaGlobalPatch(BaseModel):
338 mean_topography_srtm_alt_90m_year1994: float = None
339 mean_topography_srtm_alt_1km_year1994: float = None
340 max_topography_srtm_relative_alt_5km_year1994: float = None
341 min_topography_srtm_relative_alt_5km_year1994: float = None
342 stddev_topography_srtm_relative_alt_5km_year1994: float = None
343 climatic_zone_year2016: str = None
344 htap_region_tier1_year2010: str = None
345 dominant_landcover_year2012: str = None
346 landcover_description_25km_year2012: str = None
347 dominant_ecoregion_year2017: str = None
348 ecoregion_description_25km_year2017: str = None
349 distance_to_major_road_year2020: float = None
350 mean_stable_nightlights_1km_year2013: float = None
351 mean_stable_nightlights_5km_year2013: float = None
352 max_stable_nightlights_25km_year2013: float = None
353 max_stable_nightlights_25km_year1992: float = None
354 mean_population_density_250m_year2015: float = None
355 mean_population_density_5km_year2015: float = None
356 max_population_density_25km_year2015: float = None
357 mean_population_density_250m_year1990: float = None
358 mean_population_density_5km_year1990: float = None
359 max_population_density_25km_year1990: float = None
360 mean_nox_emissions_10km_year2015: float = None
361 mean_nox_emissions_10km_year2000: float = None
362 toar1_category: str = None
363 toar2_category: str = None
364 station_id: int = None
366 @validator('climatic_zone_year2016')
367 def check_climatic_zone(cls, v):
368 if tuple(filter(lambda x: x.string == v, toardb.toardb.CZ_vocabulary)):
369 return v
370 else:
371 raise ValueError(f"climatic zone not known: {v}")
373 @validator('toar1_category')
374 def check_toar1_category(cls, v):
375 if tuple(filter(lambda x: x.string == v, toardb.toardb.TC_vocabulary)):
376 return v
377 else:
378 raise ValueError(f"TOAR1 category not known: {v}")
380 @validator('toar2_category')
381 def check_toar2_category(cls, v):
382 if tuple(filter(lambda x: x.string == v, toardb.toardb.TA_vocabulary)):
383 return v
384 else:
385 raise ValueError(f"TOAR2 category not known: {v}")
387 @validator('htap_region_tier1_year2010')
388 def check_htap_region_tier1(cls, v):
389 if tuple(filter(lambda x: x.string == v, toardb.toardb.TR_vocabulary)):
390 return v
391 else:
392 raise ValueError(f"HTAP region TIER1 not known: {v}")
394 @validator('dominant_landcover_year2012')
395 def check_dominant_landcover_year2012(cls, v):
396 if tuple(filter(lambda x: x.string == v, toardb.toardb.LC_vocabulary)):
397 return v
398 else:
399 raise ValueError(f"dominant landcover (year2012) not known: {v}")
401 @validator('dominant_ecoregion_year2017')
402 def check_dominant_ecoregion_year2017(cls, v):
403 if tuple(filter(lambda x: x.string == v, toardb.toardb.ER_vocabulary)):
404 return v
405 else:
406 raise ValueError(f"dominant ecoregion (year2017) not known: {v}")
408 @validator('landcover_description_25km_year2012')
409 def check_landcover_description_25km_year2012(cls, v):
410 try:
411 desc = get_abbreviation_from_code_description(toardb.toardb.LC_vocabulary, v)
412 return v
413 except:
414 raise ValueError(f"landcover_description_25km_year2012 not known: {v}")
416 @validator('ecoregion_description_25km_year2017')
417 def check_ecoregion_description_25km_year2017(cls, v):
418 try:
419 desc = get_abbreviation_from_code_description(toardb.toardb.ER_vocabulary, v)
420 return v
421 except:
422 raise ValueError(f"ecoregion_description_25km_year2017 not known: {v}")
425def get_full_description_from_abbreviation (vocabulary, abbr_desc: str):
426 full_description = ''
427 if abbr_desc != '':
428 for desc in abbr_desc.split(','):
429 code, perc = desc.split(':')
430 ftype = tuple(filter(lambda x: x.value == int(code), vocabulary))[0].display_str
431 if full_description:
432 full_description += ', ' + ftype + ': ' + perc
433 else:
434 full_description = ftype + ': ' + perc
435 return full_description
438def get_code_description_from_abbreviation (vocabulary, abbr_desc: str):
439 code_description = ''
440 if abbr_desc != '':
441 for desc in abbr_desc.split(','):
442 code, perc = desc.split(':')
443 ftype = tuple(filter(lambda x: x.value == int(code.strip()), vocabulary))[0].string
444 if code_description:
445 code_description += ',' + ftype + ':' + perc.strip()
446 else:
447 code_description = ftype + ':' + perc.strip()
448 return code_description
451def get_abbreviation_from_code_description(vocabulary, full_desc: str):
452 full_description = ''
453 if full_desc != '':
454 for desc in full_desc.split(','):
455 code, perc = desc.split(':')
456 abbr = tuple(filter(lambda x: x.string == code.strip(), vocabulary))[0].value
457 if full_description:
458 full_description += ',' + str(abbr) + ':' + perc.strip()
459 else:
460 full_description = str(abbr) + ':' + perc.strip()
461 return full_description
464class StationmetaGlobalFields(StationmetaGlobalPatch):
465 pass
467 @validator('climatic_zone_year2016')
468 def check_climatic_zone(cls, v):
469 return v
471 @validator('toar1_category')
472 def check_toar1_category(cls, v):
473 return v
475 @validator('toar2_category')
476 def check_toar2_category(cls, v):
477 return v
479 @validator('htap_region_tier1_year2010')
480 def check_htap_region_tier1(cls, v):
481 return v
483 @validator('dominant_landcover_year2012')
484 def check_dominant_landcover_year2012(cls, v):
485 return v
487 @validator('dominant_ecoregion_year2017')
488 def check_dominant_ecoregion_year2017(cls, v):
489 return v
491 @validator('landcover_description_25km_year2012')
492 def check_landcover_description_25km_year2012(cls, v):
493 return v
495 @validator('ecoregion_description_25km_year2017')
496 def check_ecoregion_description_25km_year2017(cls, v):
497 return v
500class StationmetaGlobalCreate(StationmetaGlobalBase):
501 pass
503 @validator('climatic_zone_year2016')
504 def check_climatic_zone(cls, v):
505 if tuple(filter(lambda x: x.string == v, toardb.toardb.CZ_vocabulary)):
506 return v
507 else:
508 raise ValueError(f"climatic zone not known: {v}")
510 @validator('toar1_category')
511 def check_toar1_category(cls, v):
512 if tuple(filter(lambda x: x.string == v, toardb.toardb.TC_vocabulary)):
513 return v
514 else:
515 raise ValueError(f"TOAR1 category not known: {v}")
517 @validator('toar2_category')
518 def check_toar2_category(cls, v):
519 if tuple(filter(lambda x: x.string == v, toardb.toardb.TA_vocabulary)):
520 return v
521 else:
522 raise ValueError(f"TOAR2 category not known: {v}")
524 @validator('htap_region_tier1_year2010')
525 def check_htap_region_tier1(cls, v):
526 if tuple(filter(lambda x: x.string == v, toardb.toardb.TR_vocabulary)):
527 return v
528 else:
529 raise ValueError(f"HTAP region TIER1 not known: {v}")
531 @validator('dominant_landcover_year2012')
532 def check_dominant_landcover_year2012(cls, v):
533 if tuple(filter(lambda x: x.string == v, toardb.toardb.LC_vocabulary)):
534 return v
535 else:
536 raise ValueError(f"dominant landcover (year2012) not known: {v}")
538 @validator('dominant_ecoregion_year2017')
539 def check_dominant_ecoregion_year2017(cls, v):
540 if tuple(filter(lambda x: x.string == v, toardb.toardb.ER_vocabulary)):
541 return v
542 else:
543 raise ValueError(f"dominant ecoregion (year2017) not known: {v}")
546class StationmetaGlobal(StationmetaGlobalBase):
547 class Config:
548 orm_mode = True
551class StationmetaGlobalBaseNested(StationmetaGlobalBase):
552 mean_topography_srtm_alt_90m_year1994: float = None
553 mean_topography_srtm_alt_1km_year1994: float = None
554 max_topography_srtm_relative_alt_5km_year1994: float = None
555 min_topography_srtm_relative_alt_5km_year1994: float = None
556 stddev_topography_srtm_relative_alt_5km_year1994: float = None
557 climatic_zone_year2016: str = None
558 htap_region_tier1_year2010: str = None
559 dominant_landcover_year2012: str = None
560 landcover_description_25km_year2012: str = None
561 full_description_year2012: str = None
562 dominant_ecoregion_year2017: str = None
563 ecoregion_description_25km_year2017: str = None
564 distance_to_major_road_year2020: float = None
565 mean_stable_nightlights_1km_year2013: float = None
566 mean_stable_nightlights_5km_year2013: float = None
567 max_stable_nightlights_25km_year2013: float = None
568 max_stable_nightlights_25km_year1992: float = None
569 mean_population_density_250m_year2015: float = None
570 mean_population_density_5km_year2015: float = None
571 max_population_density_25km_year2015: float = None
572 mean_population_density_250m_year1990: float = None
573 mean_population_density_5km_year1990: float = None
574 max_population_density_25km_year1990: float = None
575 mean_nox_emissions_10km_year2015: float = None
576 mean_nox_emissions_10km_year2000: float = None
577 toar1_category: str = None
578 toar2_category: str = None
581class StationmetaGlobalNestedCreate(StationmetaGlobalBaseNested):
582 pass
585class StationmetaGlobalNested(StationmetaCoreBase, StationmetaGlobalBaseNested):
586 class Config:
587 orm_mode = True
590# ======== StationmetaGlobalService =========
592class StationmetaGlobalServiceBase(BaseModel):
593 variable_name: str = Field(..., description="Variable name which will be returned by the service URL")
594 service_url: str = Field(..., description="Service URL with %%LAT%% and %%LON%% placeholders to enter station or point location in floating point degrees")
597class StationmetaGlobalService(StationmetaGlobalServiceBase):
599 class Config:
600 orm_mode = True
602# ======== StationmetaRole =========
604class StationmetaRoleBase(BaseModel):
605 id: int = None
606 role: str = Field(..., description="Role of contact (see controlled vocabulary: Role Code)")
607 status: str = Field(..., description="Status of contact (see controlled vocabulary: Role Status)")
608 contact: Contact
610 @validator('role')
611 def check_role(cls, v):
612 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.RC_vocabulary))[0].display_str
614 @validator('status')
615 def check_status(cls, v):
616 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.RS_vocabulary))[0].display_str
618 @validator('contact')
619 def check_contact_is_private(cls, v):
620 if v.person:
621 if (v.person.id != 0):
622 if (v.person.isprivate):
623 return None
624 else:
625 return v.person
626 else:
627 return v.organisation
629 class Config:
630 orm_mode = True
633class StationmetaRolePatch(BaseModel):
634 role: str
635 status: str
636# at the moment contact_id has to be given...
637# ==> in the future: give unique contact_email
638# patching stationmeta should not result in creating new contacts!
639# ==> still to do: check, whether contact already exists (otherwise patching cannot be done)
640 contact_id: int
642 class Config:
643 orm_mode = True
646class StationmetaRoleCreate(StationmetaRoleBase):
647 contact_id: int
648 contact: Contact = None
650 @validator('role')
651 def check_role(cls, v):
652 if tuple(filter(lambda x: x.string == v, toardb.toardb.RC_vocabulary)):
653 return v
654 else:
655 raise ValueError(f"role code not known: {v}")
657 @validator('status')
658 def check_status(cls, v):
659 if tuple(filter(lambda x: x.string == v, toardb.toardb.RS_vocabulary)):
660 return v
661 else:
662 raise ValueError(f"role status not known: {v}")
665class StationmetaRole(StationmetaRoleBase):
666 id: int = Field(..., description="for internal use only")
667 contact: Contact = Field(..., description="Contact for this role")
669 class Config:
670 orm_mode = True
673# ======== StationmetaChangelog =========
675class StationmetaChangelogBase(BaseModel):
676 datetime: dt.datetime = Field(..., description="Date of change to the TOAR database")
677 description: str = Field(..., description="Description of change")
678 old_value: str = Field(..., description="value that has been changed in the TOAR database")
679 new_value: str = Field(..., description="new, changed value")
680 station_id: int = Field(..., description="internal ID of station to which this change belongs")
681 author_id: int = Field(..., description="internal ID of person who submitted the change to the TOAR database")
682 type_of_change: int = Field(..., description="Type of change (see controlled vocabulary: Type Of Change)")
685 @validator('type_of_change')
686 def check_role(cls, v):
687 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.CL_vocabulary))[0].display_str
689 class Config:
690 orm_mode = True
692class StationmetaChangelog(StationmetaChangelogBase):
694 class Config:
695 orm_mode = True
698# ======== for nested view/upload =========
700class StationmetaBase(StationmetaCoreBase):
701 roles: List[StationmetaRole] = None
702 annotations: List[StationmetaAnnotation] = None
703 aux_images: List[StationmetaAuxImage] = None
704 aux_docs: List[StationmetaAuxDoc] = None
705 aux_urls: List[StationmetaAuxUrl] = None
706 globalmeta: StationmetaGlobal = None
707 changelog: List[StationmetaChangelog] = None
709 class Config:
710 orm_mode = True
712 @validator('changelog')
713 def order_changelog(cls, v):
714 return sorted(v, key=lambda x: x.datetime)
716 @validator('roles')
717 def check_roles(cls, v):
718 if v == []:
719 return None
720 else:
721 return v
723 @validator('annotations')
724 def check_annotations(cls, v):
725 if v == []:
726 return None
727 else:
728 return v
731class StationmetaPatch(StationmetaCoreCreate):
732 #roles: List[StationmetaRolePatch] = None
733 #annotations: List[StationmetaAnnotationPatch] = None
734 # just to get things working
735 roles: list = None
736 annotations: list = None
737 aux_images: List[StationmetaAuxImagePatch] = None
738 aux_docs: List[StationmetaAuxDocPatch] = None
739 aux_urls: List[StationmetaAuxUrlPatch] = None
740 globalmeta: StationmetaGlobalPatch = None
742 class Config:
743 orm_mode = True
746class StationmetaCreate(StationmetaCoreCreate):
747 roles: List[StationmetaRoleCreate] = None
748 annotations: List[StationmetaAnnotationCreate] = None
749 aux_images: List[StationmetaAuxImage] = None
750 aux_docs: List[StationmetaAuxDoc] = None
751 aux_urls: List[StationmetaAuxUrl] = None
752 globalmeta: StationmetaGlobalNestedCreate = None
754 class Config:
755 orm_mode = True
758class StationmetaFields(StationmetaCorePatch):
759 roles: List[StationmetaRolePatch] = None
760 annotations: List[StationmetaAnnotationPatch] = None
761 aux_images: List[StationmetaAuxImagePatch] = None
762 aux_docs: List[StationmetaAuxDocPatch] = None
763 aux_urls: List[StationmetaAuxUrlPatch] = None
764 globalmeta: StationmetaGlobalFields = None
766 class Config:
767 orm_mode = True
770class Stationmeta(StationmetaBase):
771 id: int = Field(None, description="for internal use only")
773 class Config:
774 orm_mode = True