27#include "boost/algorithm/string/trim.hpp"
49 std::shared_ptr<table::io::Persistable>
const&);
64int constexpr SERIALIZATION_VERSION = 5;
76double getDouble(daf::base::PropertySet
const& metadata, std::string
const& key) {
78 return metadata.exists(key) && !metadata.isUndefined(key) ? metadata.getAsDouble(key) :
nan;
79 }
catch (pex::exceptions::TypeError& err) {
95lsst::geom::Angle getAngle(daf::base::PropertySet
const& metadata, std::string
const& key) {
107std::string getString(daf::base::PropertySet
const& metadata, std::string
const& key) {
108 return metadata.exists(key) && !metadata.isUndefined(key) ? metadata.getAsString(key) :
"";
119bool setDouble(daf::base::PropertySet& metadata, std::string
const& key,
double value,
120 std::string
const& comment) {
122 metadata.set(key, value);
136bool setAngle(daf::base::PropertySet& metadata, std::string
const& key, lsst::geom::Angle
const& angle,
137 std::string
const& comment) {
138 return setDouble(metadata, key, angle.
asDegrees(), comment);
149bool setString(daf::base::PropertySet& metadata, std::string
const& key, std::string value,
150 std::string
const& comment) {
151 if (!
value.empty()) {
152 metadata.set(key, value);
163std::string rotTypeStrFromEnum(RotType rotType) {
165 case RotType::UNKNOWN:
169 case RotType::HORIZON:
174 std::ostringstream os;
175 os <<
"Unknown RotType enum: " <<
static_cast<int>(rotType);
184RotType rotTypeEnumFromStr(std::string
const& rotTypeName) {
185 if (rotTypeName ==
"UNKNOWN") {
186 return RotType::UNKNOWN;
187 }
else if (rotTypeName ==
"SKY") {
189 }
else if (rotTypeName ==
"HORIZON") {
190 return RotType::HORIZON;
191 }
else if (rotTypeName ==
"MOUNT") {
192 return RotType::MOUNT;
194 std::ostringstream os;
195 os <<
"Unknown RotType name: \"" << rotTypeName <<
"\"";
199class VisitInfoSchema {
235 static std::string
const VERSION_KEY;
237 static VisitInfoSchema
const& get(
int version = SERIALIZATION_VERSION) {
239 static VisitInfoSchema instanceLatest(SERIALIZATION_VERSION);
241 static VisitInfoSchema instanceWithExposureId(4);
243 return instanceWithExposureId;
245 return instanceLatest;
250 VisitInfoSchema(
const VisitInfoSchema&) =
delete;
251 VisitInfoSchema& operator=(
const VisitInfoSchema&) =
delete;
254 VisitInfoSchema(VisitInfoSchema&&) =
delete;
255 VisitInfoSchema& operator=(VisitInfoSchema&&) =
delete;
258 VisitInfoSchema(
int _version) :
schema() {
264 exposureTime =
schema.addField<
double>(
"exposuretime",
"exposure duration",
"s");
265 darkTime =
schema.addField<
double>(
"darktime",
"time from CCD flush to readout",
"s");
266 tai =
schema.addField<std::int64_t>(
267 "tai",
"TAI date and time at middle of exposure as nsec from unix epoch",
"nsec");
268 ut1 =
schema.addField<
double>(
"ut1",
"UT1 date and time at middle of exposure",
"MJD");
269 era =
schema.addField<lsst::geom::Angle>(
"era",
"earth rotation angle at middle of exposure",
"");
271 "sky position of boresight at middle of exposure");
274 boresightAzAlt_az =
schema.addField<lsst::geom::Angle>(
276 "refracted apparent topocentric position of boresight at middle of exposure",
"");
277 boresightAzAlt_alt =
schema.addField<lsst::geom::Angle>(
278 "boresightazalt_alt",
279 "refracted apparent topocentric position of boresight at middle of exposure",
"");
280 boresightAirmass =
schema.addField<
double>(
281 "boresightairmass",
"airmass at boresight, relative to zenith at sea level",
"");
282 boresightRotAngle =
schema.addField<lsst::geom::Angle>(
283 "boresightrotangle",
"rotation angle at boresight at middle of exposure",
"");
285 schema.addField<
int>(
"rottype",
"rotation type; see VisitInfo.getRotType for details",
"MJD");
288 latitude =
schema.addField<lsst::geom::Angle>(
"latitude",
289 "latitude of telescope (+ is east of Greenwich)",
"");
290 longitude =
schema.addField<lsst::geom::Angle>(
"longitude",
"longitude of telescope",
"");
291 elevation =
schema.addField<
double>(
"elevation",
"elevation of telescope",
"");
294 airTemperature =
schema.addField<
double>(
"airtemperature",
"air temperature",
"C");
295 airPressure =
schema.addField<
double>(
"airpressure",
"air pressure",
"Pascal");
296 humidity =
schema.addField<
double>(
"humidity",
"humidity (%)",
"");
298 instrumentLabel =
schema.addField<std::string>(
299 "instrumentlabel",
"Short name of the instrument that took this data",
"", 0);
302 version =
schema.addField<
int>(VERSION_KEY,
"version of this VisitInfo");
304 idnum =
schema.addField<
table::RecordId>(
"idnum",
"identifier of this full focal plane exposure",
"");
306 focusZ =
schema.addField<
double>(
"focusz",
"defocal distance",
"mm");
308 observationType =
schema.addField<std::string>(
309 "observationType",
"type of this observation (e.g. science, flat, bias)",
"", 0);
310 scienceProgram =
schema.addField<std::string>(
311 "scienceProgram",
"observing program (survey or proposal) identifier",
"", 0);
312 observationReason =
schema.addField<std::string>(
313 "observationReason",
"reason this observation was taken, or its purpose",
"", 0);
314 object =
schema.addField<std::string>(
"object",
"object of interest or field name",
"", 0);
315 hasSimulatedContent =
schema.addField<afw::table::Flag>(
316 "hasSimulatedContent",
"Was any part of this observation simulated?");
319std::string
const VisitInfoSchema::VERSION_KEY =
"version";
323 std::shared_ptr<table::io::Persistable>
read(InputArchive
const& archive,
324 CatalogVector
const& catalogs)
const override {
328 int version = getVersion(record);
329 if (
version > SERIALIZATION_VERSION) {
330 throw LSST_EXCEPT(pex::exceptions::TypeError,
"Cannot read VisitInfo FITS version > " +
336 VisitInfoSchema
const&
keys = VisitInfoSchema::get(
version);
338 std::string instrumentLabel =
version >= 1 ? record.get(
keys.instrumentLabel) :
"";
342 std::string observationType =
version >= 4 ? record.get(
keys.observationType) :
"";
343 std::string scienceProgram =
version >= 4 ? record.get(
keys.scienceProgram) :
"";
344 std::string observationReason =
version >= 4 ? record.get(
keys.observationReason) :
"";
345 std::string
object =
version >= 4 ? record.get(
keys.object) :
"";
347 bool hasSimulatedContent =
version >= 4 ? record.get(
keys.hasSimulatedContent) :
false;
349 std::shared_ptr<VisitInfo>
result(
350 new VisitInfo(record.get(
keys.exposureTime), record.get(
keys.darkTime),
352 record.get(
keys.era), record.get(
keys.boresightRaDec),
353 lsst::geom::SpherePoint(record.get(
keys.boresightAzAlt_az),
354 record.get(
keys.boresightAzAlt_alt)),
355 record.get(
keys.boresightAirmass), record.get(
keys.boresightRotAngle),
358 record.get(
keys.elevation)),
360 record.get(
keys.humidity)),
361 instrumentLabel,
id, focusZ, observationType, scienceProgram, observationReason,
362 object, hasSimulatedContent));
366 explicit VisitInfoFactory(std::string
const& name) :
table::io::PersistableFactory(
name) {}
372 auto versionKey = record.getSchema().find<
int>(VisitInfoSchema::VERSION_KEY);
373 return record.get(versionKey.key);
374 }
catch (pex::exceptions::NotFoundError
const&) {
381std::string getVisitInfoPersistenceName() {
return "VisitInfo"; }
383VisitInfoFactory registration(getVisitInfoPersistenceName());
393 "TIME-MID",
"MJD-AVG-UT1",
"AVG-ERA",
"BORE-RA",
394 "BORE-DEC",
"BORE-AZ",
"BORE-ALT",
"BORE-AIRMASS",
395 "BORE-ROTANG",
"ROTTYPE",
"OBS-LONG",
"OBS-LAT",
396 "OBS-ELEV",
"AIRTEMP",
"AIRPRESS",
"HUMIDITY",
397 "INSTRUMENT",
"IDNUM",
"FOCUSZ",
"OBSTYPE",
398 "PROGRAM",
"REASON",
"OBJECT",
"HAS-SIMULATED-CONTENT"};
399 for (
auto&& key : keyList) {
400 if (metadata.
exists(key)) {
409 setDouble(metadata,
"EXPTIME", visitInfo.getExposureTime(),
"Exposure time (sec)");
410 setDouble(metadata,
"DARKTIME", visitInfo.getDarkTime(),
"Time from CCD flush to readout (sec)");
411 if (visitInfo.getDate().isValid()) {
413 "TAI date at middle of observation");
414 metadata.
set(
"TIMESYS",
"TAI");
416 setDouble(metadata,
"MJD-AVG-UT1", visitInfo.getUt1(),
"UT1 MJD date at ctr of obs");
417 setAngle(metadata,
"AVG-ERA", visitInfo.getEra(),
"Earth rot ang at ctr of obs (deg)");
418 auto boresightRaDec = visitInfo.getBoresightRaDec();
419 setAngle(metadata,
"BORE-RA", boresightRaDec[0],
"ICRS RA (deg) at boresight");
420 setAngle(metadata,
"BORE-DEC", boresightRaDec[1],
"ICRS Dec (deg) at boresight");
421 auto boresightAzAlt = visitInfo.getBoresightAzAlt();
422 setAngle(metadata,
"BORE-AZ", boresightAzAlt[0],
"Refr app topo az (deg) at bore");
423 setAngle(metadata,
"BORE-ALT", boresightAzAlt[1],
"Refr app topo alt (deg) at bore");
424 setDouble(metadata,
"BORE-AIRMASS", visitInfo.getBoresightAirmass(),
"Airmass at boresight");
425 setAngle(metadata,
"BORE-ROTANG", visitInfo.getBoresightRotAngle(),
"Rotation angle (deg) at boresight");
426 metadata.
set(
"ROTTYPE", rotTypeStrFromEnum(visitInfo.getRotType()),
"Type of rotation angle");
427 auto observatory = visitInfo.getObservatory();
428 setAngle(metadata,
"OBS-LONG", observatory.getLongitude(),
"Telescope longitude (+E, deg)");
429 setAngle(metadata,
"OBS-LAT", observatory.getLatitude(),
"Telescope latitude (deg)");
430 setDouble(metadata,
"OBS-ELEV", observatory.getElevation(),
"Telescope elevation (m)");
431 auto weather = visitInfo.getWeather();
432 setDouble(metadata,
"AIRTEMP", weather.getAirTemperature(),
"Outside air temperature (C)");
433 setDouble(metadata,
"AIRPRESS", weather.getAirPressure(),
"Outdoor air pressure (P)");
434 setDouble(metadata,
"HUMIDITY", weather.getHumidity(),
"Relative humidity (%)");
435 setString(metadata,
"INSTRUMENT", visitInfo.getInstrumentLabel(),
436 "Short name of the instrument that took this data");
437 if (visitInfo.getId() != 0) {
438 metadata.
set(
"IDNUM", visitInfo.getId(),
"identifier of this full focal plane exposure");
440 setDouble(metadata,
"FOCUSZ", visitInfo.getFocusZ(),
"Defocal distance (mm)");
441 setString(metadata,
"OBSTYPE", visitInfo.getObservationType(),
"Type of this observation");
442 setString(metadata,
"PROGRAM", visitInfo.getScienceProgram(),
443 "observing program (survey or proposal) identifier");
444 setString(metadata,
"REASON", visitInfo.getObservationReason(),
445 "reason this observation was taken, or its purpose");
446 setString(metadata,
"OBJECT", visitInfo.getObject(),
"object of interest or field name");
447 metadata.
set(
"HAS-SIMULATED-CONTENT", visitInfo.getHasSimulatedContent(),
448 "Was any part of this observation simulated?");
454 : _exposureTime(nan),
455 _darkTime(getDouble(metadata,
"DARKTIME")),
457 _ut1(getDouble(metadata,
"MJD-AVG-UT1")),
458 _era(getAngle(metadata,
"AVG-ERA")),
460 lsst::
geom::SpherePoint(getAngle(metadata,
"BORE-RA"), getAngle(metadata,
"BORE-DEC"))),
462 lsst::
geom::SpherePoint(getAngle(metadata,
"BORE-AZ"), getAngle(metadata,
"BORE-ALT"))),
463 _boresightAirmass(getDouble(metadata,
"BORE-AIRMASS")),
464 _boresightRotAngle(getAngle(metadata,
"BORE-ROTANG")),
466 _observatory(getAngle(metadata,
"OBS-LONG"), getAngle(metadata,
"OBS-LAT"),
467 getDouble(metadata,
"OBS-ELEV")),
468 _weather(getDouble(metadata,
"AIRTEMP"), getDouble(metadata,
"AIRPRESS"),
469 getDouble(metadata,
"HUMIDITY")),
470 _instrumentLabel(getString(metadata,
"INSTRUMENT")),
472 _focusZ(getDouble(metadata,
"FOCUSZ")),
473 _observationType(getString(metadata,
"OBSTYPE")),
474 _scienceProgram(getString(metadata,
"PROGRAM")),
475 _observationReason(getString(metadata,
"REASON")),
476 _object(getString(metadata,
"OBJECT")),
478 _hasSimulatedContent(false) {
481 _id = metadata.getAsInt64(key);
487 _exposureTime = metadata.getAsDouble(key);
496 if (metadata.exists(key) && !metadata.isUndefined(key)) {
497 if (metadata.exists(
"TIMESYS")) {
498 auto timesysName = boost::algorithm::trim_right_copy(metadata.getAsString(
"TIMESYS"));
499 if (timesysName !=
"TAI") {
503 std::ostringstream os;
504 os <<
"TIMESYS = \"" << timesysName
505 <<
"\"; VisitInfo requires TIMESYS to exist and to equal \"TAI\"";
506 throw LSST_EXCEPT(lsst::pex::exceptions::RuntimeError, os.str());
509 throw LSST_EXCEPT(lsst::pex::exceptions::RuntimeError,
510 "TIMESYS not found; VistitInfo requires TIMESYS to exist and to equal \"TAI\"");
517 if (metadata.exists(key) && !metadata.isUndefined(key)) {
523 if (metadata.exists(key) && !metadata.isUndefined(key)) {
524 _rotType = rotTypeEnumFromStr(metadata.getAsString(key));
527 key =
"HAS-SIMULATED-CONTENT";
528 if (metadata.exists(key) && !metadata.isUndefined(key)) {
529 _hasSimulatedContent = metadata.getAsBool(key);
548 return (!lhs.isFinite() && !rhs.isFinite()) || lhs == rhs;
569 _boresightAzAlt, _boresightAirmass, _boresightRotAngle, _rotType, _observatory,
570 _weather, _instrumentLabel, _id, _focusZ, _observationType, _scienceProgram,
571 _observationReason, _object, _hasSimulatedContent);
577 VisitInfoSchema
const& keys = VisitInfoSchema::get();
583 record->set(keys.ut1,
getUt1());
584 record->set(keys.era,
getEra());
587 record->set(keys.boresightAzAlt_az, boresightAzAlt[0]);
588 record->set(keys.boresightAzAlt_alt, boresightAzAlt[1]);
591 record->set(keys.rotType,
static_cast<int>(
getRotType()));
593 record->set(keys.latitude, observatory.getLatitude());
594 record->set(keys.longitude, observatory.getLongitude());
595 record->set(keys.elevation, observatory.getElevation());
597 record->set(keys.airTemperature, weather.getAirTemperature());
598 record->set(keys.airPressure, weather.getAirPressure());
599 record->set(keys.humidity, weather.getHumidity());
601 record->set(keys.version, SERIALIZATION_VERSION);
602 record->set(keys.idnum,
getId());
621 double _parallactic_y, _parallactic_x, result;
626 result = atan2(_parallactic_y, _parallactic_x);
631 return std::make_unique<VisitInfo>(*
this);
640 buffer <<
"VisitInfo(";
645 buffer <<
"UT1=" <<
getUt1() <<
", ";
646 buffer <<
"ERA=" <<
getEra() <<
", ";
651 buffer <<
"rotType=" <<
static_cast<int>(
getRotType()) <<
", ";
655 buffer <<
"id=" <<
getId() <<
", ";
656 buffer <<
"focusZ=" <<
getFocusZ() <<
", ";
660 buffer <<
"object='" <<
getObject() <<
"', ";
667 os << visitInfo.toString();
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
Hold the location of an observatory.
Basic weather information sufficient for a simple model for air mass or refraction.
Information about a single exposure of an imaging camera.
std::string getPersistenceName() const override
Return the unique name used to persist this object and look up its factory.
daf::base::DateTime getDate() const
get uniform date and time at middle of exposure
coord::Weather getWeather() const
get basic weather information
VisitInfo(double exposureTime, double darkTime, daf::base::DateTime const &date, double ut1, lsst::geom::Angle const &era, lsst::geom::SpherePoint const &boresightRaDec, lsst::geom::SpherePoint const &boresightAzAlt, double boresightAirmass, lsst::geom::Angle const &boresightRotAngle, RotType const &rotType, coord::Observatory const &observatory, coord::Weather const &weather, std::string const &instrumentLabel, table::RecordId const &id, double focusZ, std::string const &observationType, std::string const &scienceProgram, std::string const &observationReason, std::string const &object, bool hasSimulatedContent)
Construct a VisitInfo.
lsst::geom::Angle getLocalEra() const
double getUt1() const
get UT1 (universal time) MJD date at middle of exposure
double getBoresightAirmass() const
get airmass at the boresight, relative to zenith at sea level (and at the middle of the exposure,...
std::string getScienceProgram() const
lsst::geom::Angle getBoresightHourAngle() const
lsst::geom::Angle getBoresightRotAngle() const
Get rotation angle at boresight at middle of exposure.
std::size_t hash_value() const noexcept override
Return a hash of this object.
bool operator==(VisitInfo const &other) const
lsst::geom::Angle getEra() const
get earth rotation angle at middle of exposure
table::RecordId getId() const
std::string getObservationReason() const
std::string toString() const override
Create a string representation of this object.
double getExposureTime() const
get exposure duration (shutter open time); (sec)
RotType getRotType() const
get rotation type of boresightRotAngle
bool equals(typehandling::Storable const &other) const noexcept override
Compare this object to another Storable.
lsst::geom::SpherePoint getBoresightAzAlt() const
get refracted apparent topocentric Az/Alt position at the boresight (and at the middle of the exposur...
std::string getObject() const
lsst::geom::SpherePoint getBoresightRaDec() const
get ICRS RA/Dec position at the boresight (and at the middle of the exposure, if it varies with time)
std::shared_ptr< typehandling::Storable > cloneStorable() const override
Create a new VisitInfo that is a copy of this one.
std::string getInstrumentLabel() const
bool getHasSimulatedContent() const
double getDarkTime() const
get time from CCD flush to exposure readout, including shutter open time (despite the name); (sec)
coord::Observatory getObservatory() const
get observatory longitude, latitude and elevation
lsst::geom::Angle getBoresightParAngle() const
Get parallactic angle at the boresight.
void write(OutputArchiveHandle &handle) const override
Write the object to one or more catalogs.
std::string getObservationType() const
Base class for all records.
std::shared_ptr< RecordT > addNew()
Create a new record, add it to the end of the catalog, and return a pointer to it.
A FunctorKey used to get or set celestial coordinates from a pair of lsst::geom::Angle keys.
static CoordKey addFields(afw::table::Schema &schema, std::string const &name, std::string const &doc)
Add a pair of _ra, _dec fields to a Schema, and return a CoordKey that points to them.
A class used as a handle to a particular field in a table.
Defines the fields and offsets for a table.
void saveCatalog(BaseCatalog const &catalog)
Save a catalog in the archive.
BaseCatalog makeCatalog(Schema const &schema)
Return a new, empty catalog with the given schema.
static std::shared_ptr< T > dynamicCast(std::shared_ptr< Persistable > const &ptr)
Dynamically cast a shared_ptr.
A base class for factory classes used to reconstruct objects from records.
io::OutputArchiveHandle OutputArchiveHandle
Interface supporting iteration over heterogenous containers.
static bool singleClassEquals(T const &lhs, Storable const &rhs)
Test if a Storable is of a particular class and equal to another object.
Class for handling dates/times, including MJD, UTC, and TAI.
Class for storing ordered metadata with comments.
void set(std::string const &name, T const &value)
Replace all values for a property name (possibly hierarchical) with a new scalar value.
Class for storing generic metadata.
std::string getAsString(std::string const &name) const
Get the last value for a string property name (possibly hierarchical).
virtual void remove(std::string const &name)
Remove all values for a property name (possibly hierarchical).
bool isUndefined(std::string const &name) const
Determine if a name (possibly hierarchical) has a defined value.
bool exists(std::string const &name) const
Determine if a name (possibly hierarchical) exists.
A class representing an angle.
constexpr double asDegrees() const noexcept
Return an Angle's value in degrees.
Point in an unspecified spherical coordinate system.
Reports errors from accepting an object of an unexpected or inappropriate type.
int stripVisitInfoKeywords(daf::base::PropertySet &metadata)
Remove VisitInfo-related keywords from the metadata.
void setVisitInfoMetadata(daf::base::PropertyList &metadata, VisitInfo const &visitInfo)
Set FITS metadata from a VisitInfo.
bool _eqOrNonFinite(lsst::geom::SpherePoint const &lhs, lsst::geom::SpherePoint const &rhs) noexcept
Test whether two SpherePoints are exactly equal or invalid.
std::ostream & operator<<(std::ostream &os, Measurement const &measurement)
bool _eqOrNan(double lhs, double rhs) noexcept
Test whether two numbers are exactly equal or both NaN.
CatalogT< BaseRecord > BaseCatalog
std::int64_t RecordId
Type used for unique IDs for records.
std::size_t hashCombine(std::size_t seed) noexcept
Combine hashes.
AngleUnit constexpr degrees
constant with units of degrees
AngleUnit constexpr radians
constant with units of radians
std::shared_ptr< table::io::Persistable > read(table::io::InputArchive const &archive, table::io::CatalogVector const &catalogs) const override