diff --git a/igc.cc b/igc.cc index 11b2cfb6f..ba5f46af2 100644 --- a/igc.cc +++ b/igc.cc @@ -52,6 +52,7 @@ #include "grtcirc.h" // for RAD, gcdist, radtometers #include "src/core/datetime.h" // for DateTime #include "formspec.h" // for FormatSpecificData, kFsIGC +#include "kml.h" // for KmlFormat::igc_mt_fields_def @@ -60,6 +61,19 @@ #define HDRDELIM "~" #define DATEMAGIC "IGCDATE" +const QVector KmlFormat::igc_mt_fields_def = { + { IgcFormat::igc_wp_field::igc_enl, "igc_enl", "Engine Noise", "double" }, + { IgcFormat::igc_wp_field::igc_tas, "igc_tas", "True Airspd", "double" }, + { IgcFormat::igc_wp_field::igc_oat, "igc_oat", "Otsd Air Temp", "double" }, + { IgcFormat::igc_wp_field::igc_vat, "igc_vat", "Ttl Enrg Vario", "double" }, + { IgcFormat::igc_wp_field::igc_gsp, "igc_gsp", "Ground Speed", "double" }, + { IgcFormat::igc_wp_field::igc_fxa, "igc_fxa", "Fix Accuracy", "double" }, + { IgcFormat::igc_wp_field::igc_gfo, "igc_gfo", "G Force?", "double" }, + { IgcFormat::igc_wp_field::igc_acz, "igc_acz", "Z Accel", "double" }, + { IgcFormat::igc_wp_field::igc_siu, "igc_siu", "# Of Sats", "double" }, + { IgcFormat::igc_wp_field::igc_trt, "igc_trt", "True Track", "double" }, +}; + /* * See if two lat/lon pairs are approximately equal. * @param lat1 The latitude of coordinate pair 1 diff --git a/igc.h b/igc.h index 4054a9150..32473e2a1 100644 --- a/igc.h +++ b/igc.h @@ -40,7 +40,6 @@ #include "format.h" #include "gbfile.h" // for gbfprintf, gbfclose, gbfopen, gbfputs, gbfgetstr, gbfile #include "src/core/datetime.h" // for DateTime -#include "kml.h" // for wp_field /* * Notes on IGC extensions: @@ -103,6 +102,20 @@ class IgcFormat : public Format void write() override; void wr_deinit() override; + enum class igc_wp_field { + igc_enl = 0, // Engine Noise Level + igc_tas, // True Airspeed + igc_vat, // Compensated variometer (total energy) + igc_oat, // Outside Air Temperature + igc_trt, // True Track + igc_gsp, // Ground Speed + igc_fxa, // Fix Accuracy + igc_gfo, // G Force + igc_siu, // Satellites In Use + igc_acz, // Z Acceleration + }; + static constexpr int number_igc_wp_fields = static_cast(igc_wp_field::igc_acz) + 1; + private: /* Types */ @@ -298,6 +311,8 @@ struct igc_fsdata : public FormatSpecificData { std::optional acz; // Z Acceleration std::optional gfo; // G Force? + QList*, IgcFormat::igc_wp_field>> ext_list; + // Stores all data as igc_fsdata bool set_value(IgcFormat::igc_ext_type_t type, double value, Waypoint *wp = nullptr) { @@ -305,39 +320,49 @@ struct igc_fsdata : public FormatSpecificData { switch (type) { case IgcFormat::igc_ext_type_t::ext_rec_enl: enl = value; + ext_list.append(std::make_tuple(&enl, IgcFormat::igc_wp_field::igc_enl)); break; case IgcFormat::igc_ext_type_t::ext_rec_tas: tas = value; + ext_list.append(std::make_tuple(&tas, IgcFormat::igc_wp_field::igc_tas)); break; case IgcFormat::igc_ext_type_t::ext_rec_vat: vat = value; + ext_list.append(std::make_tuple(&vat, IgcFormat::igc_wp_field::igc_vat)); break; case IgcFormat::igc_ext_type_t::ext_rec_oat: if (wp){ wp->set_temperature(value); } + ext_list.append(std::make_tuple(&oat, IgcFormat::igc_wp_field::igc_oat)); oat = value; break; case IgcFormat::igc_ext_type_t::ext_rec_trt: trt = value; + ext_list.append(std::make_tuple(&trt, IgcFormat::igc_wp_field::igc_trt)); break; case IgcFormat::igc_ext_type_t::ext_rec_gsp: gsp = value; + ext_list.append(std::make_tuple(&gsp, IgcFormat::igc_wp_field::igc_gsp)); break; case IgcFormat::igc_ext_type_t::ext_rec_fxa: fxa = value; + ext_list.append(std::make_tuple(&fxa, IgcFormat::igc_wp_field::igc_fxa)); break; case IgcFormat::igc_ext_type_t::ext_rec_siu: if (wp) { wp->sat = value; } siu = value; + ext_list.append(std::make_tuple(&siu, IgcFormat::igc_wp_field::igc_siu)); break; case IgcFormat::igc_ext_type_t::ext_rec_acz: acz = value; + ext_list.append(std::make_tuple(&acz, IgcFormat::igc_wp_field::igc_acz)); break; case IgcFormat::igc_ext_type_t::ext_rec_gfo: gfo = value; + ext_list.append(std::make_tuple(&gfo, IgcFormat::igc_wp_field::igc_gfo)); break; default: success = false; @@ -345,39 +370,38 @@ struct igc_fsdata : public FormatSpecificData { return success; } - // Not currently used, but already written and left for future use. - std::optional get_value(IgcFormat::igc_ext_type_t defn_type) const + std::optional get_value(IgcFormat::igc_wp_field defn_type) const { std::optional ret; switch (defn_type) { - case IgcFormat::igc_ext_type_t::ext_rec_enl: + case IgcFormat::igc_wp_field::igc_enl: ret = enl; break; - case IgcFormat::igc_ext_type_t::ext_rec_tas: + case IgcFormat::igc_wp_field::igc_tas: ret = tas; break; - case IgcFormat::igc_ext_type_t::ext_rec_vat: + case IgcFormat::igc_wp_field::igc_vat: ret = vat; break; - case IgcFormat::igc_ext_type_t::ext_rec_oat: + case IgcFormat::igc_wp_field::igc_oat: ret = oat; break; - case IgcFormat::igc_ext_type_t::ext_rec_trt: + case IgcFormat::igc_wp_field::igc_trt: ret = trt; break; - case IgcFormat::igc_ext_type_t::ext_rec_gsp: + case IgcFormat::igc_wp_field::igc_gsp: ret = gsp; break; - case IgcFormat::igc_ext_type_t::ext_rec_fxa: + case IgcFormat::igc_wp_field::igc_fxa: ret = fxa; break; - case IgcFormat::igc_ext_type_t::ext_rec_siu: + case IgcFormat::igc_wp_field::igc_siu: ret = siu; break; - case IgcFormat::igc_ext_type_t::ext_rec_acz: + case IgcFormat::igc_wp_field::igc_acz: ret = acz; break; - case IgcFormat::igc_ext_type_t::ext_rec_gfo: + case IgcFormat::igc_wp_field::igc_gfo: ret = gfo; break; default: @@ -386,46 +410,6 @@ struct igc_fsdata : public FormatSpecificData { } return ret; } - std::optional get_value(KmlFormat::wp_field defn_type) const - { - std::optional ret; - switch (defn_type) { - case KmlFormat::wp_field::igc_enl: - ret = enl; - break; - case KmlFormat::wp_field::igc_tas: - ret = tas; - break; - case KmlFormat::wp_field::igc_vat: - ret = vat; - break; - case KmlFormat::wp_field::igc_oat: - ret = oat; - break; - case KmlFormat::wp_field::igc_trt: - ret = trt; - break; - case KmlFormat::wp_field::igc_gsp: - ret = gsp; - break; - case KmlFormat::wp_field::igc_fxa: - ret = fxa; - break; - case KmlFormat::wp_field::igc_siu: - ret = siu; - break; - case KmlFormat::wp_field::igc_acz: - ret = acz; - break; - case KmlFormat::wp_field::igc_gfo: - ret = gfo; - break; - default: - fatal("igc.h: igc_fsdata::get_value(KmlFormat::wp_field defn_type): Invalid wp_field\n"); - break; - } - return ret; - } }; #endif // IGC_H_INCLUDED_ diff --git a/kml.cc b/kml.cc index 92245bf0a..e6b81daaa 100644 --- a/kml.cc +++ b/kml.cc @@ -72,16 +72,6 @@ #define MYNAME "kml" const QVector KmlFormat::mt_fields_def = { - { wp_field::igc_enl, "igc_enl", "Engine Noise", "double" }, - { wp_field::igc_tas, "igc_tas", "True Airspd", "double" }, - { wp_field::igc_oat, "igc_oat", "Otsd Air Temp", "double" }, - { wp_field::igc_vat, "igc_vat", "Ttl Enrg Vario", "double" }, - { wp_field::igc_gsp, "igc_gsp", "Ground Speed", "double" }, - { wp_field::igc_fxa, "igc_fxa", "Fix Accuracy", "double" }, - { wp_field::igc_gfo, "igc_gfo", "G Force?", "double" }, - { wp_field::igc_acz, "igc_acz", "Z Accel", "double" }, - { wp_field::igc_siu, "igc_siu", "# Of Sats", "double" }, - { wp_field::igc_trt, "igc_trt", "True Track", "double" }, { wp_field::cadence, "cadence", "Cadence", "int" }, { wp_field::depth, "depth", "Depth", "float" }, { wp_field::heartrate, "heartrate", "Heart Rate", "int" }, @@ -1487,11 +1477,13 @@ void KmlFormat::kml_track_tlr(const route_head* header) * Unlike every other format, we do the bulk of the work in the header * callback as we have to make multiple passes over the track queues. */ - +template void KmlFormat::kml_mt_simple_array(const route_head* header, const QString& name, - wp_field member) const + T fld_supertype) const { + using wp_fld_type = typename std::underlying_type::type; + writer->writeStartElement(QStringLiteral("gx:SimpleArrayData")); writer->writeAttribute(QStringLiteral("name"), name); if (global_opts.debug_level >= 3) { @@ -1499,43 +1491,40 @@ void KmlFormat::kml_mt_simple_array(const route_head* header, } foreach (const Waypoint* wpt, header->waypoint_list) { const auto* fs_igc = reinterpret_cast(wpt->fs.FsChainFind(kFsIGC)); - switch (member) { - case wp_field::power: - writer->writeTextElement(QStringLiteral("gx:value"), wpt->power? - QString::number(wpt->power, 'f', 1) : QString()); - break; - case wp_field::cadence: - writer->writeTextElement(QStringLiteral("gx:value"), wpt->cadence? - QString::number(wpt->cadence) : QString()); - break; - case wp_field::depth: - writer->writeTextElement(QStringLiteral("gx:value"), wpt->depth_has_value()? - QString::number(wpt->depth_value(), 'f', 1) : QString()); - break; - case wp_field::heartrate: - writer->writeTextElement(QStringLiteral("gx:value"), wpt->heartrate? - QString::number(wpt->heartrate) : QString()); - break; - case wp_field::temperature: - writer->writeTextElement(QStringLiteral("gx:value"), wpt->temperature_has_value()? - QString::number(wpt->temperature_value(), 'f', 1) : QString()); - break; - case wp_field::sat: - writer->writeTextElement(QStringLiteral("gx:value"), wpt->sat >= 0? - QString::number(wpt->sat) : QString()); - break; - case wp_field::igc_enl: - case wp_field::igc_tas: - case wp_field::igc_vat: - case wp_field::igc_oat: - case wp_field::igc_trt: - case wp_field::igc_gsp: - case wp_field::igc_fxa: - case wp_field::igc_gfo: - case wp_field::igc_siu: - case wp_field::igc_acz: - if (fs_igc && fs_igc->get_value(member).has_value()) { - double value = fs_igc->get_value(member).value(); + wp_fld_type fld_type = static_cast(fld_supertype); + + if constexpr (std::is_same_v) { + switch (fld_type) { + case static_cast(wp_field::power): + writer->writeTextElement(QStringLiteral("gx:value"), wpt->power? + QString::number(wpt->power, 'f', 1) : QString()); + break; + case static_cast(wp_field::cadence): + writer->writeTextElement(QStringLiteral("gx:value"), wpt->cadence? + QString::number(wpt->cadence) : QString()); + break; + case static_cast(wp_field::depth): + writer->writeTextElement(QStringLiteral("gx:value"), wpt->depth_has_value()? + QString::number(wpt->depth_value(), 'f', 1) : QString()); + break; + case static_cast(wp_field::heartrate): + writer->writeTextElement(QStringLiteral("gx:value"), wpt->heartrate? + QString::number(wpt->heartrate) : QString()); + break; + case static_cast(wp_field::temperature): + writer->writeTextElement(QStringLiteral("gx:value"), wpt->temperature_has_value()? + QString::number(wpt->temperature_value(), 'f', 1) : QString()); + break; + case static_cast(wp_field::sat): + writer->writeTextElement(QStringLiteral("gx:value"), wpt->sat >= 0? + QString::number(wpt->sat) : QString()); + break; + default: + fatal(MYNAME ": Bad wp_field type (%i) in kml_mt_simple_array()\n", fld_type); + } + } else if constexpr (std::is_same_v) { + if (fs_igc && fs_igc->get_value(static_cast(fld_supertype)).has_value()) { + double value = fs_igc->get_value(static_cast(fld_supertype)).value(); if (global_opts.debug_level >= 6) { printf(MYNAME ": Writing KML SimpleArray data: %s of %f\n", qPrintable(name), value); } @@ -1548,9 +1537,8 @@ void KmlFormat::kml_mt_simple_array(const route_head* header, } writer->writeTextElement(QStringLiteral("gx:value"), QString()); } - break; - default: - fatal("Bad member type"); + } else { + fatal(MYNAME ": Unknown wp_field type (%i) in kml_mt_simple_array\n", fld_type); } } writer->writeEndElement(); // Close SimpleArrayData tag @@ -1585,6 +1573,7 @@ void KmlFormat::write_as_linestring(const route_head* header) void KmlFormat::kml_accumulate_track_traits(const route_head* rte) { track_trait_t track_traits; + igc_track_trait_t igc_track_traits; foreach (const Waypoint* tpt, rte->waypoint_list) { const auto* fs_igc = reinterpret_cast(tpt->fs.FsChainFind(kFsIGC)); @@ -1611,48 +1600,19 @@ void KmlFormat::kml_accumulate_track_traits(const route_head* rte) track_traits[static_cast(wp_field::sat)] = true; } if (fs_igc) { - if (fs_igc->enl.has_value()) { - track_traits[static_cast(wp_field::igc_enl)] = true; - } - if (fs_igc->tas.has_value()) { - track_traits[static_cast(wp_field::igc_tas)] = true; - } - if (fs_igc->oat.has_value()) { - track_traits[static_cast(wp_field::igc_oat)] = true; - } - if (fs_igc->vat.has_value()) { - track_traits[static_cast(wp_field::igc_vat)] = true; - } - if (fs_igc->gsp.has_value()) { - track_traits[static_cast(wp_field::igc_gsp)] = true; - } - if (fs_igc->fxa.has_value()) { - track_traits[static_cast(wp_field::igc_fxa)] = true; - } - if (fs_igc->gfo.has_value()) { - track_traits[static_cast(wp_field::igc_gfo)] = true; - } - if (fs_igc->acz.has_value()) { - track_traits[static_cast(wp_field::igc_acz)] = true; - } - if constexpr(kIncludeIGCSIU) { - if (fs_igc->siu.has_value()) { - track_traits[static_cast(wp_field::igc_siu)] = true; - } - } - if constexpr(kIncludeIGCTRT) { - if (fs_igc->trt.has_value()) { - track_traits[static_cast(wp_field::igc_trt)] = true; + for (const auto& [ptr, wp_field]: fs_igc->ext_list) { + if (ptr->has_value()) { + igc_track_traits[static_cast(wp_field)] = true; } } } } // For dual source fields give priority to igc. - if (track_traits[static_cast(wp_field::igc_oat)]) { + if (igc_track_traits[static_cast(IgcFormat::igc_wp_field::igc_oat)]) { track_traits[static_cast(wp_field::temperature)] = false; } - if (track_traits[static_cast(wp_field::igc_siu)]) { + if (igc_track_traits[static_cast(IgcFormat::igc_wp_field::igc_siu)]) { track_traits[static_cast(wp_field::sat)] = false; } @@ -1661,6 +1621,8 @@ void KmlFormat::kml_accumulate_track_traits(const route_head* rte) // If not, insert a new key value pair. kml_track_traits_hash.insert(rte, track_traits); kml_track_traits |= track_traits; + igc_kml_track_traits_hash.insert(rte, igc_track_traits); + igc_kml_track_traits |= igc_track_traits; } void KmlFormat::kml_mt_hdr(const route_head* header) @@ -1708,11 +1670,18 @@ void KmlFormat::kml_mt_hdr(const route_head* header) auto track_traits = kml_track_traits_hash.value(header); - if (track_traits.any()) { + auto igc_track_traits = igc_kml_track_traits_hash.value(header); + if (track_traits.any() || igc_track_traits.any()) { writer->writeStartElement(QStringLiteral("ExtendedData")); writer->writeStartElement(QStringLiteral("SchemaData")); writer->writeAttribute(QStringLiteral("schemaUrl"), QStringLiteral("#schema")); + for (const auto& flddef : igc_mt_fields_def) { + if (igc_track_traits[static_cast(flddef.id)]) { + kml_mt_simple_array(header, flddef.name, flddef.id); + } + } + for (const auto& flddef : mt_fields_def) { if (track_traits[static_cast(flddef.id)]) { kml_mt_simple_array(header, flddef.name, flddef.id); @@ -1904,20 +1873,29 @@ void KmlFormat::write() if (export_track) { kml_track_traits.reset(); kml_track_traits_hash.clear(); + igc_kml_track_traits.reset(); + igc_kml_track_traits_hash.clear(); auto kml_accumulate_track_traits_lambda = [this](const route_head* rte)->void { kml_accumulate_track_traits(rte); }; track_disp_all(kml_accumulate_track_traits_lambda, nullptr, nullptr); - if (kml_track_traits.any()) { + if (kml_track_traits.any() || igc_kml_track_traits.any()) { writer->writeStartElement(QStringLiteral("Schema")); writer->writeAttribute(QStringLiteral("id"), QStringLiteral("schema")); + // Write schema headers for generic waypoint data for (const auto& flddef : mt_fields_def) { if (kml_track_traits[static_cast(flddef.id)]) { kml_mt_array_schema(flddef.name, flddef.displayName, flddef.type); } } + // Write schema headers for IGC specific data + for (const auto& flddef : igc_mt_fields_def) { + if (igc_kml_track_traits[static_cast(flddef.id)]) { + kml_mt_array_schema(flddef.name, flddef.displayName, flddef.type); + } + } writer->writeEndElement(); // Close Schema tag } } diff --git a/kml.h b/kml.h index 1262fe35c..e720dadff 100644 --- a/kml.h +++ b/kml.h @@ -38,7 +38,7 @@ #include "src/core/xmlstreamwriter.h" // for XmlStreamWriter #include "units.h" // for UnitsFormatter #include "xmlgeneric.h" // for cb_cdata, cb_end, cb_start, xg_callback, xg_string, xg_cb_type, xml_deinit, xml_ignore_tags, xml_init, xml_read, xg_tag_mapping - +#include "igc.h" class KmlFormat : public Format { @@ -77,18 +77,22 @@ class KmlFormat : public Format temperature, power, sat, - igc_enl, // Engine Noise Level - igc_tas, // True Airspeed - igc_vat, // Compensated variometer (total energy) - igc_oat, // Outside Air Temperature - igc_trt, // True Track - igc_gsp, // Ground Speed - igc_fxa, // Fix Accuracy - igc_gfo, // G Force - igc_siu, // Satellites In Use - igc_acz // Z Acceleration }; - static constexpr int number_wp_fields = static_cast(wp_field::igc_acz) + 1; + static constexpr int number_wp_fields = static_cast(wp_field::sat) + 1; + + struct igc_mt_field_t { + IgcFormat::igc_wp_field id; + const QString name; + const QString displayName; + const QString type; + }; + + struct mt_field_t { + wp_field id; + const QString name; + const QString displayName; + const QString type; + }; private: /* Types */ @@ -102,14 +106,8 @@ class KmlFormat : public Format kmlpt_other }; - struct mt_field_t { - wp_field id; - const QString name; - const QString displayName; - const QString type; - }; - using track_trait_t = std::bitset; + using igc_track_trait_t = std::bitset; /* Constants */ static constexpr const char* default_precision = "6"; @@ -130,10 +128,6 @@ class KmlFormat : public Format nullptr }; - // IGC option compile-time flags - static constexpr bool kIncludeIGCSIU = true; - static constexpr bool kIncludeIGCTRT = false; - /* Member Functions */ void kml_init_color_sequencer(unsigned int steps_per_rev); @@ -189,7 +183,9 @@ class KmlFormat : public Format void kml_track_hdr(const route_head* header) const; void kml_track_disp(const Waypoint* waypointp) const; void kml_track_tlr(const route_head* header); - void kml_mt_simple_array(const route_head* header, const QString& name, wp_field member) const; + + template + void kml_mt_simple_array(const route_head* header, const QString& name, T member) const; static bool track_has_time(const route_head* header); void write_as_linestring(const route_head* header); void kml_accumulate_track_traits(const route_head* rte); @@ -205,8 +201,11 @@ class KmlFormat : public Format /* Data Members */ static const QVector mt_fields_def; + static const QVector igc_mt_fields_def; track_trait_t kml_track_traits; + igc_track_trait_t igc_kml_track_traits; QHash kml_track_traits_hash; + QHash igc_kml_track_traits_hash; // options char* opt_deficon{nullptr};