mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-19 13:03:36 +00:00
refactor charge sockets data processing into dedicated class
While here, add method to generate the OSM keys corresponding to a socket list. Signed-off-by: Séverin Lemaignan <severin@guakamole.org>
This commit is contained in:
@@ -251,6 +251,7 @@ target_link_libraries(${PROJECT_NAME}
|
|||||||
storage
|
storage
|
||||||
descriptions
|
descriptions
|
||||||
indexer
|
indexer
|
||||||
|
feature_helpers
|
||||||
cppjansson
|
cppjansson
|
||||||
expat::expat
|
expat::expat
|
||||||
tess2
|
tess2
|
||||||
|
|||||||
@@ -84,191 +84,6 @@ bool Prefix2Double(std::string const & str, double & d)
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // 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<std::string, std::string> 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<std::string> 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)
|
std::string MetadataTagProcessorImpl::ValidateAndFormat_stars(std::string const & v)
|
||||||
{
|
{
|
||||||
if (v.empty())
|
if (v.empty())
|
||||||
@@ -709,9 +524,9 @@ MetadataTagProcessor::~MetadataTagProcessor()
|
|||||||
if (!m_description.IsEmpty())
|
if (!m_description.IsEmpty())
|
||||||
m_params.GetMetadata().Set(feature::Metadata::FMD_DESCRIPTION, m_description.GetBuffer());
|
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);
|
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_SELF_SERVICE: valid = ValidateAndFormat_self_service(v); break;
|
||||||
case Metadata::FMD_OUTDOOR_SEATING: valid = ValidateAndFormat_outdoor_seating(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_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.
|
// Metadata types we do not get from OSM.
|
||||||
case Metadata::FMD_CUISINE:
|
case Metadata::FMD_CUISINE:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "indexer/feature_data.hpp"
|
#include "indexer/feature_data.hpp"
|
||||||
#include "indexer/validate_and_format_contacts.hpp"
|
#include "indexer/validate_and_format_contacts.hpp"
|
||||||
|
#include "feature_helpers/feature_charge_sockets.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -9,23 +10,6 @@ struct MetadataTagProcessorImpl
|
|||||||
{
|
{
|
||||||
MetadataTagProcessorImpl(FeatureBuilderParams & params) : m_params(params) {}
|
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
|
|
||||||
* <type>|<nb>|[<power>];...
|
|
||||||
*
|
|
||||||
* For instance:
|
|
||||||
* "type2_combo|2|150;chademo|1|50;type2|2|"
|
|
||||||
*/
|
|
||||||
std::string StringifyChargeSockets() const;
|
|
||||||
std::string ValidateAndFormat_maxspeed(std::string const & v) const;
|
std::string ValidateAndFormat_maxspeed(std::string const & v) const;
|
||||||
static std::string ValidateAndFormat_stars(std::string const & v);
|
static std::string ValidateAndFormat_stars(std::string const & v);
|
||||||
std::string ValidateAndFormat_operator(std::string const & v) const;
|
std::string ValidateAndFormat_operator(std::string const & v) const;
|
||||||
@@ -62,20 +46,9 @@ struct MetadataTagProcessorImpl
|
|||||||
static std::string ValidateAndFormat_outdoor_seating(std::string v);
|
static std::string ValidateAndFormat_outdoor_seating(std::string v);
|
||||||
|
|
||||||
protected:
|
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<ChargeSocketDescriptor> ChargeSocketDescriptors;
|
|
||||||
|
|
||||||
// stores information about charge sockets in charging stations.
|
// stores information about charge sockets in charging stations.
|
||||||
// Incrementally completed in AggregateChargeSocket
|
ChargeSocketsHelper m_chargeSockets;
|
||||||
ChargeSocketDescriptors m_chargeSockets;
|
|
||||||
|
|
||||||
FeatureBuilderParams & m_params;
|
FeatureBuilderParams & m_params;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ add_subdirectory(descriptions)
|
|||||||
add_subdirectory(drape)
|
add_subdirectory(drape)
|
||||||
add_subdirectory(drape_frontend)
|
add_subdirectory(drape_frontend)
|
||||||
add_subdirectory(editor)
|
add_subdirectory(editor)
|
||||||
|
add_subdirectory(feature_helpers)
|
||||||
add_subdirectory(ge0)
|
add_subdirectory(ge0)
|
||||||
add_subdirectory(mwm_diff)
|
add_subdirectory(mwm_diff)
|
||||||
add_subdirectory(geometry)
|
add_subdirectory(geometry)
|
||||||
|
|||||||
14
libs/feature_helpers/CMakeLists.txt
Normal file
14
libs/feature_helpers/CMakeLists.txt
Normal file
@@ -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)
|
||||||
392
libs/feature_helpers/feature_charge_sockets.cpp
Normal file
392
libs/feature_helpers/feature_charge_sockets.cpp
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
|
||||||
|
#include "feature_helpers/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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<std::string, std::string> 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<std::pair<std::string, std::string>> result;
|
||||||
|
|
||||||
|
for (auto const & s : m_chargeSockets)
|
||||||
|
{
|
||||||
|
// Only produce if type is non-empty
|
||||||
|
if (!s.type.empty())
|
||||||
|
{
|
||||||
|
// socket.<type> = 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.<type>.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;
|
||||||
|
}
|
||||||
102
libs/feature_helpers/feature_charge_sockets.hpp
Normal file
102
libs/feature_helpers/feature_charge_sockets.hpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <utility> // for std::pair
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// 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<ChargeSocketDescriptor> ChargeSocketDescriptors;
|
||||||
|
|
||||||
|
typedef std::vector<std::pair<std::string, std::string>> OSMKeyValues;
|
||||||
|
|
||||||
|
class ChargeSocketsHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ChargeSocketsHelper() : m_dirty(false) {}
|
||||||
|
|
||||||
|
/** Create a ChareSocketsHelper instance from an existing list of sockets
|
||||||
|
* stored as "<type>|<nb>|[<power>];..."
|
||||||
|
*
|
||||||
|
* 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 "<type>|<nb>|[<power>];..."
|
||||||
|
*
|
||||||
|
* 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<std::string_view, 12> 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;
|
||||||
|
};
|
||||||
@@ -158,6 +158,7 @@ target_link_libraries(${PROJECT_NAME}
|
|||||||
geometry
|
geometry
|
||||||
protobuf
|
protobuf
|
||||||
coding
|
coding
|
||||||
|
feature_helpers
|
||||||
)
|
)
|
||||||
|
|
||||||
omim_add_test_subdirectory(indexer_tests)
|
omim_add_test_subdirectory(indexer_tests)
|
||||||
|
|||||||
@@ -180,74 +180,8 @@ std::string_view MapObject::GetOpeningHours() const
|
|||||||
|
|
||||||
ChargeSocketDescriptors MapObject::GetChargeSockets() const
|
ChargeSocketDescriptors MapObject::GetChargeSockets() const
|
||||||
{
|
{
|
||||||
ChargeSocketDescriptors sockets;
|
|
||||||
|
|
||||||
auto s = std::string(m_metadata.Get(MetadataID::FMD_CHARGE_SOCKETS));
|
auto s = std::string(m_metadata.Get(MetadataID::FMD_CHARGE_SOCKETS));
|
||||||
if (s.empty())
|
return ChargeSocketsHelper(s).GetSockets();
|
||||||
return sockets;
|
|
||||||
|
|
||||||
// pre-set order of socket types preference (from high-power to low-power).
|
|
||||||
static std::vector<std::string> 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
feature::Internet MapObject::GetInternet() const
|
feature::Internet MapObject::GetInternet() const
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#include "coding/string_utf8_multilang.hpp"
|
#include "coding/string_utf8_multilang.hpp"
|
||||||
|
|
||||||
|
#include "feature_helpers/feature_charge_sockets.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -17,17 +19,6 @@ namespace osm
|
|||||||
{
|
{
|
||||||
class EditableMapObject;
|
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<ChargeSocketDescriptor> ChargeSocketDescriptors;
|
|
||||||
|
|
||||||
class MapObject
|
class MapObject
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
Reference in New Issue
Block a user