From 0c0e9ab5f6c90850610fd7e10fabf1cf417d593f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9verin=20Lemaignan?= Date: Sun, 28 Sep 2025 20:51:49 +0200 Subject: [PATCH] refactor charge sockets data processing into dedicated class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While here, add method to generate the OSM keys corresponding to a socket list. Signed-off-by: Séverin Lemaignan --- generator/CMakeLists.txt | 1 + generator/osm2meta.cpp | 191 +-------- generator/osm2meta.hpp | 31 +- libs/CMakeLists.txt | 1 + libs/feature_helpers/CMakeLists.txt | 14 + .../feature_charge_sockets.cpp | 392 ++++++++++++++++++ .../feature_charge_sockets.hpp | 102 +++++ libs/indexer/CMakeLists.txt | 1 + libs/indexer/map_object.cpp | 68 +-- libs/indexer/map_object.hpp | 13 +- 10 files changed, 519 insertions(+), 295 deletions(-) create mode 100644 libs/feature_helpers/CMakeLists.txt create mode 100644 libs/feature_helpers/feature_charge_sockets.cpp create mode 100644 libs/feature_helpers/feature_charge_sockets.hpp diff --git a/generator/CMakeLists.txt b/generator/CMakeLists.txt index b1af498d3..df9b19811 100644 --- a/generator/CMakeLists.txt +++ b/generator/CMakeLists.txt @@ -251,6 +251,7 @@ target_link_libraries(${PROJECT_NAME} storage descriptions indexer + feature_helpers cppjansson expat::expat tess2 diff --git a/generator/osm2meta.cpp b/generator/osm2meta.cpp index a13b6e35e..1c7f5876e 100644 --- a/generator/osm2meta.cpp +++ b/generator/osm2meta.cpp @@ -84,191 +84,6 @@ bool Prefix2Double(std::string const & str, double & d) } } // namespace -void MetadataTagProcessorImpl::AggregateChargeSocket(std::string const & k, std::string const & v) -{ - auto keys = strings::Tokenize(k, ":"); - ASSERT(keys[0] == "socket", ()); // key must start with "socket:" - if (keys.size() < 2 || keys.size() > 3) - { - LOG(LWARNING, ("Invalid socket key:", k)); - return; - } - - std::string type(keys[1]); - - bool isOutput = false; - if (keys.size() == 3) - { - if (keys[2] == "output") - isOutput = true; - else - return; // ignore other suffixes - } - - // normalize type if needed - // based on recommandations from https://wiki.openstreetmap.org/wiki/Key:socket:* - static std::unordered_map const kTypeMap = { - {"tesla_supercharger", "nacs"}, // also used in EU for 'type2_combo' -> needs fix in OSM tagging - {"tesla_destination", "nacs"}, - {"tesla_standard", "nacs"}, - {"tesla", "nacs"}, - {"tesla_supercharger_ccs", "type2_combo"}, - {"ccs", "type2_combo"}, - {"type1_cable", "type1"}, - }; - - auto itMap = kTypeMap.find(type); - if (itMap != kTypeMap.end()) - type = itMap->second; - - // only store sockets type that are relevant to EV charging - static std::unordered_set const SUPPORTED_TYPES = { - "type1", "type1_combo", "type2", "type2_cable", "type2_combo", "chademo", "nacs", - "gb_ac", "gb_dc", "chaoji", "type3a", "type3c", "mcs"}; - - if (SUPPORTED_TYPES.find(type) == SUPPORTED_TYPES.end()) - return; // unknown type -> ignore - - // find or create descriptor - auto it = std::find_if(m_chargeSockets.begin(), m_chargeSockets.end(), - [&](ChargeSocketDescriptor const & d) { return d.type == type; }); - - if (it == m_chargeSockets.end()) - { - m_chargeSockets.push_back({type, "y", ""}); - it = std::prev(m_chargeSockets.end()); - } - - ASSERT(v.size() > 0, ("empty value for socket key!")); - - if (!isOutput) - { - if (v == "yes") - { - it->count = "y"; - } - else - { - // try to parse count as a number - try - { - auto count = std::stoi(v); - if (count <= 0) - { - LOG(LWARNING, ("Invalid socket count. Removing this socket.", "")); - m_chargeSockets.pop_back(); - return; - } - } - catch (...) - { - // ignore sockets with invalid counts (ie, can not be parsed to int) - // note that if a valid power output is later set for this socket, - // the socket will be re-created with a default count of 'y' - LOG(LWARNING, ("Invalid count of charging socket. Removing it.", v)); - m_chargeSockets.pop_back(); - return; - } - it->count = v; - } - } - else // isOutput == true => parse output power - { - // example value string: "44;22kW;11kva;7400w" - - std::string powerValues = strings::MakeLowerCase(v); - - // replace all occurances of 'VA' by the more standard 'W' unit - size_t pos = powerValues.find("va"); - while (pos != powerValues.npos) - { - powerValues.replace(pos, 2, "w"); - pos = powerValues.find("va", pos + 1); - } - - // if a given socket type is present several times in the same charging - // station with different power outputs, the power outputs would be concatenated - // with ';' - auto powerTokens = strings::Tokenize(powerValues, ";/"); - - // TODO: for now, we only handle the *first* provided - // power output. - std::string num(powerTokens[0]); - strings::Trim(num); - - if (num == "unknown") - { - it->output_kW = ""; - return; - } - - enum PowerUnit - { - WATT, - KILOWATT, - MEGAWATT - }; - PowerUnit unit = KILOWATT; // if no unit, kW are assumed - - if (num.size() > 2) - { - // do we have a unit? - if (num.back() == 'w') - { - unit = WATT; - num.pop_back(); - if (num.back() == 'k') - { - unit = KILOWATT; - num.pop_back(); - } - else if (num.back() == 'm') - { - unit = MEGAWATT; - num.pop_back(); - } - } - } - - strings::Trim(num); - try - { - double value = std::stod(num); - std::ostringstream oss; - switch (unit) - { - case WATT: oss << value / 1000.; break; - case MEGAWATT: oss << value * 1000; break; - case KILOWATT: oss << value; break; - } - num = oss.str(); - } - catch (...) - { - LOG(LWARNING, ("Invalid charging socket power value:", v)); - num = ""; - } - - it->output_kW = num; - } -} - -std::string MetadataTagProcessorImpl::StringifyChargeSockets() const -{ - std::ostringstream oss; - - for (size_t i = 0; i < m_chargeSockets.size(); ++i) - { - auto const & desc = m_chargeSockets[i]; - - oss << desc.type << "|" << desc.count << "|" << desc.output_kW; - - if (i + 1 < m_chargeSockets.size()) - oss << ";"; - } - return oss.str(); -} - std::string MetadataTagProcessorImpl::ValidateAndFormat_stars(std::string const & v) { if (v.empty()) @@ -709,9 +524,9 @@ MetadataTagProcessor::~MetadataTagProcessor() if (!m_description.IsEmpty()) m_params.GetMetadata().Set(feature::Metadata::FMD_DESCRIPTION, m_description.GetBuffer()); - if (!m_chargeSockets.empty()) + if (!m_chargeSockets.GetSockets().empty()) { - auto socketsList = StringifyChargeSockets(); + auto socketsList = m_chargeSockets.ToString(); m_params.GetMetadata().Set(feature::Metadata::FMD_CHARGE_SOCKETS, socketsList); } } @@ -821,7 +636,7 @@ void MetadataTagProcessor::operator()(std::string const & k, std::string const & case Metadata::FMD_SELF_SERVICE: valid = ValidateAndFormat_self_service(v); break; case Metadata::FMD_OUTDOOR_SEATING: valid = ValidateAndFormat_outdoor_seating(v); break; case Metadata::FMD_NETWORK: valid = ValidateAndFormat_operator(v); break; - case Metadata::FMD_CHARGE_SOCKETS: AggregateChargeSocket(k, v); break; + case Metadata::FMD_CHARGE_SOCKETS: m_chargeSockets.AggregateChargeSocketKey(k, v); break; // Metadata types we do not get from OSM. case Metadata::FMD_CUISINE: diff --git a/generator/osm2meta.hpp b/generator/osm2meta.hpp index 028cbb2d9..b04fbaf5d 100644 --- a/generator/osm2meta.hpp +++ b/generator/osm2meta.hpp @@ -2,6 +2,7 @@ #include "indexer/feature_data.hpp" #include "indexer/validate_and_format_contacts.hpp" +#include "feature_helpers/feature_charge_sockets.hpp" #include @@ -9,23 +10,6 @@ struct MetadataTagProcessorImpl { MetadataTagProcessorImpl(FeatureBuilderParams & params) : m_params(params) {} - /** Parse OSM attributes for socket types and add them to m_chargeSockets. - * - * Examples of (k,v) pairs: - * ("socket:type2_combo", "2") - * ("socket:type2_combo:output", "150 kW") - * ("socket:chademo", "1") - * ("socket:chademo:output", "50") // assumes kW - */ - void AggregateChargeSocket(std::string const & k, std::string const & v); - - /** Output the list of all sockets for a given charging station in the format - * ||[];... - * - * For instance: - * "type2_combo|2|150;chademo|1|50;type2|2|" - */ - std::string StringifyChargeSockets() const; std::string ValidateAndFormat_maxspeed(std::string const & v) const; static std::string ValidateAndFormat_stars(std::string const & v); std::string ValidateAndFormat_operator(std::string const & v) const; @@ -62,20 +46,9 @@ struct MetadataTagProcessorImpl static std::string ValidateAndFormat_outdoor_seating(std::string v); protected: - // struct to store the representation of a charging station socket - struct ChargeSocketDescriptor - { - std::string type; // https://wiki.openstreetmap.org/wiki/Key:socket:* - // e.g. "type1" - std::string count; // number of sockets or 'y' if OSM tag was set to 'yes'. - // ("" if unknown) - std::string output_kW; // optional power output, in kW ("" if unknown) - }; - typedef std::vector ChargeSocketDescriptors; // stores information about charge sockets in charging stations. - // Incrementally completed in AggregateChargeSocket - ChargeSocketDescriptors m_chargeSockets; + ChargeSocketsHelper m_chargeSockets; FeatureBuilderParams & m_params; }; diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 00a05fa9d..08e961a6a 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(descriptions) add_subdirectory(drape) add_subdirectory(drape_frontend) add_subdirectory(editor) +add_subdirectory(feature_helpers) add_subdirectory(ge0) add_subdirectory(mwm_diff) add_subdirectory(geometry) diff --git a/libs/feature_helpers/CMakeLists.txt b/libs/feature_helpers/CMakeLists.txt new file mode 100644 index 000000000..45306e649 --- /dev/null +++ b/libs/feature_helpers/CMakeLists.txt @@ -0,0 +1,14 @@ +project(feature_helpers) + +set(SRC + feature_charge_sockets.cpp + feature_charge_sockets.hpp +) + +omim_add_library(${PROJECT_NAME} ${SRC}) +target_link_libraries(${PROJECT_NAME} + PRIVATE + base +) + +# omim_add_test_subdirectory(feature_helpers_tests) diff --git a/libs/feature_helpers/feature_charge_sockets.cpp b/libs/feature_helpers/feature_charge_sockets.cpp new file mode 100644 index 000000000..16bc24ff4 --- /dev/null +++ b/libs/feature_helpers/feature_charge_sockets.cpp @@ -0,0 +1,392 @@ + +#include "feature_helpers/feature_charge_sockets.hpp" + +#include "base/logging.hpp" +#include "base/string_utils.hpp" + +#include +#include // for formatting doubles +#include // for std::to_string alternative +#include +#include + +// helper to format doubles (avoids trailing zeros) +inline std::string to_string_trimmed(double value, int precision = 2) +{ + std::ostringstream oss; + oss << std::fixed << std::setprecision(precision) << value; + auto str = oss.str(); + + // Remove trailing zeros + auto pos = str.find('.'); + if (pos != std::string::npos) + { + // erase trailing zeros + while (!str.empty() && str.back() == '0') + str.pop_back(); + // if we end with a '.', remove it too + if (!str.empty() && str.back() == '.') + str.pop_back(); + } + return str; +} + +// we use a custom tokenizer, as strings::Tokenize discards empty +// tokens and we want to keep them: +// +// Example: +// "a||" → ["a", "", ""] +// "b|0|" → ["b", "0", ""] +// "c||34" → ["c", "", "34"] +// "d|43|54" → ["d", "43", "54"] +std::vector tokenize(std::string_view s, char delim = '|') +{ + std::vector tokens; + size_t start = 0; + while (true) + { + size_t pos = s.find(delim, start); + if (pos == std::string_view::npos) + { + tokens.emplace_back(s.substr(start)); + break; + } + tokens.emplace_back(s.substr(start, pos - start)); + start = pos + 1; + } + return tokens; +} + +ChargeSocketsHelper::ChargeSocketsHelper(std::string const & socketsList) : m_dirty(false) +{ + if (socketsList.empty()) + return; + + auto tokens = tokenize(socketsList, ';'); + + for (auto token : tokens) + { + if (token.empty()) + continue; + + auto fields = tokenize(token, '|'); + + if (fields.size() != 3) + continue; // invalid entry, skip + + ChargeSocketDescriptor desc; + + if (std::find(SUPPORTED_TYPES.begin(), SUPPORTED_TYPES.end(), fields[0]) != SUPPORTED_TYPES.end()) + desc.type = fields[0]; + else + desc.type = UNKNOWN; + + try + { + desc.count = std::stoi(std::string(fields[1])); + } + catch (...) + { + desc.count = 0; + } + + if (fields.size() >= 3) + { + try + { + desc.power = std::stod(std::string(fields[2])); + } + catch (...) + { + desc.power = 0; + } + } + else + desc.power = 0; + + m_chargeSockets.push_back(desc); + } + m_dirty = true; +} + +void ChargeSocketsHelper::AddSocket(std::string const & type, unsigned int count, double power) +{ + if (power < 0) + { + LOG(LWARNING, ("Invalid socket power. Must be >= 0:", power)); + return; + } + + m_chargeSockets.push_back({type, count, power}); + m_dirty = true; +} + +void ChargeSocketsHelper::AggregateChargeSocketKey(std::string const & k, std::string const & v) +{ + auto keys = strings::Tokenize(k, ":"); + ASSERT(keys[0] == "socket", ()); // key must start with "socket:" + if (keys.size() < 2 || keys.size() > 3) + { + LOG(LWARNING, ("Invalid socket key:", k)); + return; + } + + std::string type(keys[1]); + + bool isOutput = false; + if (keys.size() == 3) + { + if (keys[2] == "output") + isOutput = true; + else + return; // ignore other suffixes + } + + // normalize type if needed + // based on recommandations from https://wiki.openstreetmap.org/wiki/Key:socket:* + static std::unordered_map const kTypeMap = { + // also sometimes used in EU for 'type2_combo' + // -> those cases would require correcting the OSM tagging + {"tesla_supercharger", "nacs"}, + {"tesla_destination", "nacs"}, + {"tesla_standard", "nacs"}, + {"tesla", "nacs"}, + {"tesla_supercharger_ccs", "type2_combo"}, + {"ccs", "type2_combo"}, + {"type1_cable", "type1"}, + }; + + auto itMap = kTypeMap.find(type); + if (itMap != kTypeMap.end()) + type = itMap->second; + + if (std::find(SUPPORTED_TYPES.begin(), SUPPORTED_TYPES.end(), type) == SUPPORTED_TYPES.end()) + type = UNKNOWN; + + // find or create descriptor + auto it = std::find_if(m_chargeSockets.begin(), m_chargeSockets.end(), + [&](ChargeSocketDescriptor const & d) { return d.type == type; }); + + if (it == m_chargeSockets.end()) + { + m_chargeSockets.push_back({type, 0, 0}); + it = std::prev(m_chargeSockets.end()); + } + + ASSERT(v.size() > 0, ("empty value for socket key!")); + + if (!isOutput) + { + if (v == "yes") + { + it->count = 0; + } + else + { + // try to parse count as a number + int count; + try + { + count = std::stoi(v); + if (count <= 0) + { + LOG(LWARNING, ("Invalid socket count. Removing this socket.", "")); + // TODO(skadge): incorrect behaviour if invalid count while modifying an existing socket that is not the last + // one! + m_chargeSockets.pop_back(); + return; + } + } + catch (...) + { + // ignore sockets with invalid counts (ie, can not be parsed to int) + // note that if a valid power output is later set for this socket, + // the socket will be re-created with a default count of 'y' + LOG(LWARNING, ("Invalid count of charging socket. Removing it.", v)); + // TODO(skadge): incorrect behaviour if invalid count while modifying an existing socket that is not the last + // one! + m_chargeSockets.pop_back(); + return; + } + it->count = count; + } + } + else // isOutput == true => parse output power + { + // example value string: "44;22kW;11kva;7400w" + + std::string powerValues = strings::MakeLowerCase(v); + + // replace all occurances of 'VA' by the more standard 'W' unit + size_t pos = powerValues.find("va"); + while (pos != powerValues.npos) + { + powerValues.replace(pos, 2, "w"); + pos = powerValues.find("va", pos + 1); + } + + // if a given socket type is present several times in the same charging + // station with different power outputs, the power outputs would be concatenated + // with ';' + auto powerTokens = strings::Tokenize(powerValues, ";/"); + + // TODO: for now, we only handle the *first* provided + // power output. + std::string num(powerTokens[0]); + strings::Trim(num); + + if (num == "unknown") + { + it->power = 0; + m_dirty = true; + return; + } + + enum PowerUnit + { + WATT, + KILOWATT, + MEGAWATT + }; + PowerUnit unit = KILOWATT; // if no unit, kW are assumed + + if (num.size() > 2) + { + // do we have a unit? + if (num.back() == 'w') + { + unit = WATT; + num.pop_back(); + if (num.back() == 'k') + { + unit = KILOWATT; + num.pop_back(); + } + else if (num.back() == 'm') + { + unit = MEGAWATT; + num.pop_back(); + } + } + } + + strings::Trim(num); + double value; + try + { + value = std::stod(num); + if (value <= 0) + { + LOG(LWARNING, ("Invalid charging socket power value:", v)); + // TODO(skadge): incorrect behaviour if invalid count while modifying an existing socket that is not the last + // one! + m_chargeSockets.pop_back(); + return; + } + + std::ostringstream oss; + switch (unit) + { + case WATT: value = value / 1000.; break; + case MEGAWATT: value = value * 1000; break; + case KILOWATT: break; + } + } + catch (...) + { + LOG(LWARNING, ("Invalid charging socket power value:", v)); + // TODO(skadge): incorrect behaviour if invalid count while modifying an existing socket that is not the last + // one! + m_chargeSockets.pop_back(); + return; + } + + it->power = value; + } + + m_dirty = true; +} + +ChargeSocketDescriptors ChargeSocketsHelper::GetSockets() +{ + if (m_dirty) + Sort(); + + return m_chargeSockets; +} + +std::string ChargeSocketsHelper::ToString() +{ + if (m_dirty) + Sort(); + + std::ostringstream oss; + + for (size_t i = 0; i < m_chargeSockets.size(); ++i) + { + auto const & desc = m_chargeSockets[i]; + + oss << desc.type << "|"; + if (desc.count > 0) + oss << desc.count; + oss << "|"; + if (desc.power > 0) + oss << desc.power; + + if (i + 1 < m_chargeSockets.size()) + oss << ";"; + } + return oss.str(); +} + +OSMKeyValues ChargeSocketsHelper::GetOSMKeyValues() +{ + if (m_dirty) + Sort(); + + std::vector> result; + + for (auto const & s : m_chargeSockets) + { + // Only produce if type is non-empty + if (!s.type.empty()) + { + // socket. = count + if (s.count > 0) + { + result.emplace_back("socket:" + s.type, std::to_string(s.count)); + } + else if (s.count == 0) + { + // special "yes" meaning present, but unknown count + result.emplace_back("socket:" + s.type, "yes"); + } + + // socket..output = power + if (s.power > 0.0) + result.emplace_back("socket:" + s.type + ":output", to_string_trimmed(s.power)); + } + } + + return result; +} + +void ChargeSocketsHelper::Sort() +{ + size_t const unknownTypeOrder = SUPPORTED_TYPES.size(); + + std::sort(m_chargeSockets.begin(), m_chargeSockets.end(), + [&](ChargeSocketDescriptor const & a, ChargeSocketDescriptor const & b) + { + auto const itA = std::find(SUPPORTED_TYPES.begin(), SUPPORTED_TYPES.end(), a.type); + auto const orderA = (itA == SUPPORTED_TYPES.end()) ? unknownTypeOrder : std::distance(SUPPORTED_TYPES.begin(), itA); + auto const itB = std::find(SUPPORTED_TYPES.begin(), SUPPORTED_TYPES.end(), b.type); + auto const orderB = (itB == SUPPORTED_TYPES.end()) ? unknownTypeOrder : std::distance(SUPPORTED_TYPES.begin(), itB); + + if (orderA != orderB) + return orderA < orderB; + return a.power > b.power; // Sort by power in descending order for sockets of the same type + }); + + m_dirty = false; +} diff --git a/libs/feature_helpers/feature_charge_sockets.hpp b/libs/feature_helpers/feature_charge_sockets.hpp new file mode 100644 index 000000000..1002535ee --- /dev/null +++ b/libs/feature_helpers/feature_charge_sockets.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include // for std::pair +#include + +// struct to store the representation of a charging station socket +struct ChargeSocketDescriptor +{ + std::string type; // https://wiki.openstreetmap.org/wiki/Key:socket:* + // e.g. "type1" + unsigned int count; // number of sockets; 0 means socket present, but unknown count + // (eg, OSM tag for count set to 'yes') + double power; // power output, in kW. 0 means unknown. +}; + +typedef std::vector ChargeSocketDescriptors; + +typedef std::vector> OSMKeyValues; + +class ChargeSocketsHelper +{ +public: + ChargeSocketsHelper() : m_dirty(false) {} + + /** Create a ChareSocketsHelper instance from an existing list of sockets + * stored as "||[];..." + * + * For instance: + * "type2_combo|2|150;chademo|1|50;type2|4|" + */ + ChargeSocketsHelper(std::string const & socketsList); + + void AddSocket(std::string const & type, unsigned int count, double power); + + /** Parse OSM attributes for socket types and add them to m_chargeSockets. + * + * Examples of (k,v) pairs: + * ("socket:type2_combo", "2") + * ("socket:type2_combo:output", "150 kW") + * ("socket:chademo", "1") + * ("socket:chademo:output", "50") // assumes kW + */ + void AggregateChargeSocketKey(std::string const & key, std::string const & value); + + /** Returns the current list of sockets, ordered from the most powerful to the least. + * + * The list is guaranteed to be sorted first by socket type (same ordered as + * SUPPORTED_TYPES), then by power (descending). + * + * Note that this method is not const as it may trigger a re-ordering of the + * internal list of sockets. + */ + ChargeSocketDescriptors GetSockets(); + + /** Serialize the list of sockets into a string with format "||[];..." + * + * For instance: + * "type2_combo|2|150;chademo|1|50;type2|4|" + * + * The list is guaranteed to be sorted first by socket type (same ordered as + * SUPPORTED_TYPES), then by power (descending). + * + * The same string can be use to re-create a the list of socket by creating a + * new instance of ChargeSocketsHelper with the + * ChargeSocketsHelper::ChargeSocketsHelper(std::string) ctor. + * + * Note that this method is not const as it may trigger a re-ordering of the + * internal list of sockets. + */ + std::string ToString(); + + /** Returns a list of OpenStreetMap (key, value) corresponding to the list of sockets. + * + * The list is guaranteed to be sorted first by socket type (same ordered as + * SUPPORTED_TYPES), then by power (descending). + */ + + OSMKeyValues GetOSMKeyValues(); + + inline static std::string const UNKNOWN = "unknown"; + + /** List of supported sockets, ~ordered from high-power to low-power. + * This order can be used in the UIs. + * + * Note that this method is not const as it may trigger a re-ordering of the + * internal list of sockets. + */ + static constexpr std::array SUPPORTED_TYPES = { + "mcs", "type2_combo", "chademo", "nacs", "type1", "gb_dc", + "chaoji", "type3c", "type2_cable", "type2", "gb_ac", "type3a"}; + +protected: + ChargeSocketDescriptors m_chargeSockets; + +private: + /** sort sockets: first by type, then by power (descending). + */ + void Sort(); + bool m_dirty; +}; diff --git a/libs/indexer/CMakeLists.txt b/libs/indexer/CMakeLists.txt index feccdb2c9..81b51ef0a 100644 --- a/libs/indexer/CMakeLists.txt +++ b/libs/indexer/CMakeLists.txt @@ -158,6 +158,7 @@ target_link_libraries(${PROJECT_NAME} geometry protobuf coding + feature_helpers ) omim_add_test_subdirectory(indexer_tests) diff --git a/libs/indexer/map_object.cpp b/libs/indexer/map_object.cpp index d1ca51ac2..bf669a3c5 100644 --- a/libs/indexer/map_object.cpp +++ b/libs/indexer/map_object.cpp @@ -180,74 +180,8 @@ std::string_view MapObject::GetOpeningHours() const ChargeSocketDescriptors MapObject::GetChargeSockets() const { - ChargeSocketDescriptors sockets; - auto s = std::string(m_metadata.Get(MetadataID::FMD_CHARGE_SOCKETS)); - if (s.empty()) - return sockets; - - // pre-set order of socket types preference (from high-power to low-power). - static std::vector const kSocketTypeOrder = {"type2_combo", "chademo", "nacs", - "type1", "type2_cable", "type2"}; - size_t const unknownTypeOrder = kSocketTypeOrder.size(); - - auto tokens = strings::Tokenize(s, ";"); - - for (auto token : tokens) - { - if (token.empty()) - continue; - - auto fields = strings::Tokenize(token, "|"); - - if (fields.size() < 3) - continue; // invalid entry, skip - - ChargeSocketDescriptor desc; - desc.type = fields[0]; - - try - { - desc.count = std::stoi(std::string(fields[1])); - } - catch (...) - { - desc.count = 0; - } - - if (fields.size() >= 3) - { - try - { - desc.power = std::stod(std::string(fields[2])); - } - catch (...) - { - desc.power = 0; - } - } - else - desc.power = 0; - - sockets.push_back(desc); - } - - // Sort sockets: first by type, then by power (descending). - std::sort(sockets.begin(), sockets.end(), [&](ChargeSocketDescriptor const & a, ChargeSocketDescriptor const & b) - { - auto const itA = std::find(kSocketTypeOrder.begin(), kSocketTypeOrder.end(), a.type); - auto const orderA = - (itA == kSocketTypeOrder.end()) ? unknownTypeOrder : std::distance(kSocketTypeOrder.begin(), itA); - auto const itB = std::find(kSocketTypeOrder.begin(), kSocketTypeOrder.end(), b.type); - auto const orderB = - (itB == kSocketTypeOrder.end()) ? unknownTypeOrder : std::distance(kSocketTypeOrder.begin(), itB); - - if (orderA != orderB) - return orderA < orderB; - return a.power > b.power; // Sort by power in descending order for sockets of the same type - }); - - return sockets; + return ChargeSocketsHelper(s).GetSockets(); } feature::Internet MapObject::GetInternet() const diff --git a/libs/indexer/map_object.hpp b/libs/indexer/map_object.hpp index 1b612fe20..d7164fd89 100644 --- a/libs/indexer/map_object.hpp +++ b/libs/indexer/map_object.hpp @@ -10,6 +10,8 @@ #include "coding/string_utf8_multilang.hpp" +#include "feature_helpers/feature_charge_sockets.hpp" + #include #include @@ -17,17 +19,6 @@ namespace osm { class EditableMapObject; -// struct to store the representation of a charging station socket -struct ChargeSocketDescriptor -{ - std::string type; // https://wiki.openstreetmap.org/wiki/Key:socket:* - // e.g. "type1" - unsigned int count; // number of sockets; 0 means socket present, but unknown count - // (eg, OSM tag for count set to 'yes') - double power; // power output, in kW. 0 means unknown. -}; -typedef std::vector ChargeSocketDescriptors; - class MapObject { public: