#include "indexer/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; } try { desc.power = std::stod(std::string(fields[2])); } catch (...) { 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 static std::unordered_map const kTypeMap = { {"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; // Tokenize counts or powers auto tokens = strings::Tokenize(v, ";"); if (tokens.empty()) return; if (!isOutput) { // Parse counts std::vector counts; for (auto & t : tokens) { strings::Trim(t); if (t.empty()) continue; if (t == "yes") counts.push_back(0); else { try { auto c = std::stoi(std::string(t)); if (c > 0) counts.push_back(c); else { LOG(LWARNING, ("Invalid count. Ignoring:", t)); continue; } } catch (...) { LOG(LWARNING, ("Invalid count. Ignoring:", t)); continue; } } } // Update existing sockets or add new ones for (size_t i = 0; i < counts.size(); ++i) { // Find the i-th socket of this type size_t matched = 0; auto it = std::find_if(m_chargeSockets.begin(), m_chargeSockets.end(), [&](ChargeSocketDescriptor const & d) { if (d.type != type) return false; if (matched == i) return true; ++matched; return false; }); if (it != m_chargeSockets.end()) it->count = counts[i]; else m_chargeSockets.push_back({type, counts[i], 0}); } } else { // Parse powers std::vector powers; for (auto & t : tokens) { strings::Trim(t); if (t.empty()) continue; std::string s = strings::MakeLowerCase(std::string(t)); size_t pos = s.find("va"); while (pos != std::string::npos) { s.replace(pos, 2, "w"); pos = s.find("va", pos + 1); } double value = 0; enum PowerUnit { WATT, KILOWATT, MEGAWATT } unit = KILOWATT; if (!s.empty() && s.back() == 'w') { unit = WATT; s.pop_back(); if (!s.empty() && s.back() == 'k') { unit = KILOWATT; s.pop_back(); } else if (!s.empty() && s.back() == 'm') { unit = MEGAWATT; s.pop_back(); } } try { value = std::stod(s); if (value < 0) { LOG(LWARNING, ("Invalid power value. Ignoring:", t)); continue; } switch (unit) { case WATT: value /= 1000.; break; case MEGAWATT: value *= 1000.; break; case KILOWATT: break; } } catch (...) { LOG(LWARNING, ("Invalid power value. Ignoring:", t)); continue; } powers.push_back(value); } // Update existing sockets or add new ones for (size_t i = 0; i < powers.size(); ++i) { size_t matched = 0; auto it = std::find_if(m_chargeSockets.begin(), m_chargeSockets.end(), [&](ChargeSocketDescriptor const & d) { if (d.type != type) return false; if (matched == i) return true; ++matched; return false; }); if (it != m_chargeSockets.end()) it->power = powers[i]; else m_chargeSockets.push_back({type, 0, powers[i]}); // default count=0 } } 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; std::string lastType; std::ostringstream countStream; std::ostringstream powerStream; bool firstCount = true; bool firstPower = true; for (auto const & s : m_chargeSockets) { if (s.type.empty()) continue; // If we switch type, flush previous streams if (s.type != lastType && !lastType.empty()) { result.emplace_back("socket:" + lastType, countStream.str()); if (powerStream.str().size() > 0) result.emplace_back("socket:" + lastType + ":output", powerStream.str()); countStream.str(""); countStream.clear(); powerStream.str(""); powerStream.clear(); firstCount = true; firstPower = true; } lastType = s.type; // Append count if (!firstCount) countStream << ";"; countStream << ((s.count > 0) ? std::to_string(s.count) : "yes"); firstCount = false; // Append power if > 0 if (s.power > 0) { if (!firstPower) powerStream << ";"; powerStream << to_string_trimmed(s.power); firstPower = false; } } // Flush last type if (!lastType.empty()) { result.emplace_back("socket:" + lastType, countStream.str()); if (powerStream.str().size() > 0) result.emplace_back("socket:" + lastType + ":output", powerStream.str()); } return result; } KeyValueDiffSet ChargeSocketsHelper::Diff(ChargeSocketDescriptors const & oldSockets) { KeyValueDiffSet result; auto oldKeys = ChargeSocketsHelper(oldSockets).GetOSMKeyValues(); auto newKeys = GetOSMKeyValues(); std::unordered_map oldMap, newMap; for (auto && [k, v] : oldKeys) oldMap.emplace(k, v); for (auto && [k, v] : newKeys) newMap.emplace(k, v); // Handle keys that exist in old for (auto && [k, oldVal] : oldMap) if (auto it = newMap.find(k); it == newMap.end()) result.insert({k, oldVal, ""}); // deleted else if (it->second != oldVal) result.insert({k, oldVal, it->second}); // updated // Handle keys that are only new for (auto && [k, newVal] : newMap) if (!oldMap.contains(k)) result.insert({k, "", newVal}); // created 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; }