co-maps/libs/indexer/feature_charge_sockets.cpp
2025-11-22 13:58:55 +01:00

445 lines
10 KiB
C++

#include "indexer/feature_charge_sockets.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include <algorithm>
#include <iomanip> // for formatting doubles
#include <sstream> // for std::to_string alternative
#include <string_view>
#include <vector>
// 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<std::string> tokenize(std::string_view s, char delim = '|')
{
std::vector<std::string> 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<std::string_view, std::string_view> 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<unsigned int> 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<double> 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<std::pair<std::string, std::string>> 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<std::string, std::string> 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;
}