diff --git a/app/dataapi/domain/parameterized_query.py b/app/dataapi/domain/parameterized_query.py index 856755bd..b0506998 100644 --- a/app/dataapi/domain/parameterized_query.py +++ b/app/dataapi/domain/parameterized_query.py @@ -15,6 +15,7 @@ catalogs.RawCatalog.NATURE, catalogs.RawCatalog.NOTE, catalogs.RawCatalog.PHOTOMETRY__TOTAL, + catalogs.RawCatalog.GEOMETRY, ] diff --git a/app/dataapi/model.py b/app/dataapi/model.py index e8668aa7..a87558cc 100644 --- a/app/dataapi/model.py +++ b/app/dataapi/model.py @@ -72,6 +72,27 @@ class PhotometryTotalCatalog: measurements: list[PhotometryTotalMeasurement] +@dataclass +class GeometryMeasurement: + band: str + method: str + level: float | None + a: float | None + e_a: float | None + b: float | None + e_b: float | None + pa: float | None + e_pa: float | None + isophote: float | None + e_isophote: float | None + source: Source + + +@dataclass +class GeometryCatalog: + measurements: list[GeometryMeasurement] + + @dataclass class Catalogs: designation: DesignationCatalog | None = None @@ -81,6 +102,7 @@ class Catalogs: nature: NatureCatalog | None = None notes: NotesCatalog | None = None photometry_total: PhotometryTotalCatalog | None = None + geometry: GeometryCatalog | None = None @dataclass @@ -94,6 +116,8 @@ class Layer2Object: "AdditionalDesignationsCatalog", "Catalogs", "DesignationCatalog", + "GeometryCatalog", + "GeometryMeasurement", "ICRSCatalog", "Layer2Object", "NatureCatalog", diff --git a/app/dataapi/repository/repository.py b/app/dataapi/repository/repository.py index 87874e5d..c748d320 100644 --- a/app/dataapi/repository/repository.py +++ b/app/dataapi/repository/repository.py @@ -219,6 +219,7 @@ def query_pgc( nature_task: concurrency.TaskResult[dict[int, model.NatureCatalog]] | None = None notes_task: concurrency.TaskResult[dict[int, model.NotesCatalog]] | None = None photometry_total_task: concurrency.TaskResult[dict[int, model.PhotometryTotalCatalog]] | None = None + geometry_task: concurrency.TaskResult[dict[int, model.GeometryCatalog]] | None = None if catalogs.RawCatalog.DESIGNATION in raw_catalogs: designation_task = errgr.run(self._query_designations, pgcs_page) @@ -234,6 +235,8 @@ def query_pgc( notes_task = errgr.run(self._query_notes, pgcs_page) if catalogs.RawCatalog.PHOTOMETRY__TOTAL in raw_catalogs: photometry_total_task = errgr.run(self._query_photometry_total, pgcs_page) + if catalogs.RawCatalog.GEOMETRY in raw_catalogs: + geometry_task = errgr.run(self._query_geometry, pgcs_page) errgr.wait() @@ -246,6 +249,7 @@ def query_pgc( nature_map = nature_task.result() if nature_task is not None else {} notes_map = notes_task.result() if notes_task is not None else {} photometry_total_map = photometry_total_task.result() if photometry_total_task is not None else {} + geometry_map = geometry_task.result() if geometry_task is not None else {} return [ _layer2_object_from_maps( @@ -258,6 +262,7 @@ def query_pgc( nature_map, notes_map, photometry_total_map, + geometry_map, ) for pgc in pgcs_page ] @@ -404,6 +409,39 @@ def _query_photometry_total(self, pgcs: list[int]) -> dict[int, model.Photometry result.setdefault(pgc, []).append(measurement) return {pgc: model.PhotometryTotalCatalog(measurements=measurements) for pgc, measurements in result.items()} + def _query_geometry(self, pgcs: list[int]) -> dict[int, model.GeometryCatalog]: + if not pgcs: + return {} + rows = self._storage.query( + """ + SELECT pgc, band, method, level, a, e_a, b, e_b, pa, e_pa, isophote, e_isophote, + code, year, author, title + FROM layer2.photometry_ellipse + WHERE pgc = ANY(%s) + ORDER BY pgc, band, method + """, + params=[pgcs], + ) + result: dict[int, list[model.GeometryMeasurement]] = {} + for row in rows: + pgc = int(row["pgc"]) + measurement = model.GeometryMeasurement( + band=str(row["band"]), + method=str(row["method"]), + level=float(row["level"]) if row.get("level") is not None else None, + a=float(row["a"]) if row.get("a") is not None else None, + e_a=float(row["e_a"]) if row.get("e_a") is not None else None, + b=float(row["b"]) if row.get("b") is not None else None, + e_b=float(row["e_b"]) if row.get("e_b") is not None else None, + pa=float(row["pa"]) if row.get("pa") is not None else None, + e_pa=float(row["e_pa"]) if row.get("e_pa") is not None else None, + isophote=float(row["isophote"]) if row.get("isophote") is not None else None, + e_isophote=float(row["e_isophote"]) if row.get("e_isophote") is not None else None, + source=_source_from_row(row), + ) + result.setdefault(pgc, []).append(measurement) + return {pgc: model.GeometryCatalog(measurements=measurements) for pgc, measurements in result.items()} + def _driving_table(search_types: Mapping[str, repofilters.Filter]) -> str | None: tables: set[str] = set() @@ -548,6 +586,7 @@ def _layer2_object_from_maps( nature_map: dict[int, model.NatureCatalog], notes_map: dict[int, model.NotesCatalog], photometry_total_map: dict[int, model.PhotometryTotalCatalog], + geometry_map: dict[int, model.GeometryCatalog], ) -> model.Layer2Object: designation = designation_map.get(pgc) if catalogs.RawCatalog.DESIGNATION in raw_catalogs else None additional_designations = ( @@ -558,6 +597,7 @@ def _layer2_object_from_maps( nature = nature_map.get(pgc) if catalogs.RawCatalog.NATURE in raw_catalogs else None notes = notes_map.get(pgc) if catalogs.RawCatalog.NOTE in raw_catalogs else None photometry_total = photometry_total_map.get(pgc) if catalogs.RawCatalog.PHOTOMETRY__TOTAL in raw_catalogs else None + geometry = geometry_map.get(pgc) if catalogs.RawCatalog.GEOMETRY in raw_catalogs else None return model.Layer2Object( pgc=pgc, @@ -569,6 +609,7 @@ def _layer2_object_from_maps( nature=nature, notes=notes, photometry_total=photometry_total, + geometry=geometry, ), ) diff --git a/app/dataapi/responders/structured_responder.py b/app/dataapi/responders/structured_responder.py index 09dd8fff..79a41d20 100644 --- a/app/dataapi/responders/structured_responder.py +++ b/app/dataapi/responders/structured_responder.py @@ -90,6 +90,28 @@ def _photometry_total_measurement(measurement: model.PhotometryTotalMeasurement) ) +def _geometry_measurement(measurement: model.GeometryMeasurement) -> spec.GeometryMeasurement: + return spec.GeometryMeasurement( + band=measurement.band, + method=measurement.method, + level=measurement.level, + a=measurement.a, + e_a=measurement.e_a, + b=measurement.b, + e_b=measurement.e_b, + pa=measurement.pa, + e_pa=measurement.e_pa, + isophote=measurement.isophote, + e_isophote=measurement.e_isophote, + source=spec.Source( + bibcode=measurement.source.bibcode, + title=measurement.source.title, + authors=measurement.source.authors, + year=measurement.source.year, + ), + ) + + class StructuredResponder(interface.ObjectResponder): def __init__(self, cfg: CatalogConfig, reddening_service: reddening.Reddening) -> None: self.config = cfg @@ -275,6 +297,11 @@ def build_response(self, objects: list[model.Layer2Object]) -> Any: for measurement in obj.catalogs.photometry_total.measurements ] + if obj.catalogs.geometry is not None: + catalogs.geometry = [ + _geometry_measurement(measurement) for measurement in obj.catalogs.geometry.measurements + ] + if corrected_photometry := corrected_photometry_by_pgc.get(obj.pgc): catalogs.photometry_total_corrected = corrected_photometry diff --git a/app/specs/dataapi/interface.py b/app/specs/dataapi/interface.py index 1c0a190a..5cd22ab7 100644 --- a/app/specs/dataapi/interface.py +++ b/app/specs/dataapi/interface.py @@ -21,6 +21,7 @@ "EquatorialCoordinatesUnits", "GalacticCoordinates", "GalacticCoordinatesUnits", + "GeometryMeasurement", "J2000Coordinate", "ListReddeningReferencesResponse", "Nature", @@ -131,6 +132,21 @@ class PhotometryTotalMeasurement(pydantic.BaseModel): e_mag: float | None +class GeometryMeasurement(pydantic.BaseModel): + band: str + method: str + level: float | None + a: float | None + e_a: float | None + b: float | None + e_b: float | None + pa: float | None + e_pa: float | None + isophote: float | None + e_isophote: float | None + source: Source + + class Nature(pydantic.BaseModel): type_name: str @@ -145,6 +161,7 @@ class Catalogs(pydantic.BaseModel): notes: list[NoteEntry] | None = None photometry_total: list[PhotometryTotalMeasurement] | None = None photometry_total_corrected: list[PhotometryTotalMeasurement] | None = None + geometry: list[GeometryMeasurement] | None = None class PGCObject(pydantic.BaseModel): diff --git a/postgres/migrations/V072__photometry_ellipse_layer2_view.sql b/postgres/migrations/V072__photometry_ellipse_layer2_view.sql new file mode 100644 index 00000000..157b1527 --- /dev/null +++ b/postgres/migrations/V072__photometry_ellipse_layer2_view.sql @@ -0,0 +1,24 @@ +CREATE VIEW layer2.photometry_ellipse AS +SELECT + r.pgc +, e.band +, e.method +, e.level +, e.a +, e.e_a +, e.b +, e.e_b +, e.pa +, e.e_pa +, e.isophote +, e.e_isophote +, t.bib +, b.code +, b.year +, b.author +, b.title +FROM photometry.ellipse AS e + JOIN layer0.records AS r ON e.record_id = r.id + LEFT JOIN layer0.tables AS t ON r.table_id = t.id + LEFT JOIN common.bib AS b ON t.bib = b.id +WHERE r.pgc IS NOT NULL;