geostat/common.cpp

358 wiersze
12 KiB
C++

#include "common.h"
#include "cache.h"
#include "debug.h"
#include <algorithm>
#include <numeric>
#include <iterator>
void htmlencode(std::string& data) {
std::string tmp;
tmp.reserve(data.size() + 20);
for (size_t i = 0; i != data.size(); i++) {
switch (data[i]) {
case '&':
tmp += "&amp;";
break;
case '\"':
tmp += "&quot;";
break;
case '\'':
tmp += "&apos;";
break;
case '<':
tmp += "&lt;";
break;
case '>':
tmp += "&gt;";
break;
default:
tmp += data[i];
break;
}
}
data.swap(tmp);
}
constexpr static std::string string_mangle(const std::string& str) {
std::string tmp;
tmp.reserve(str.size());
for (size_t i = 0; i != str.size(); i++) {
if (str[i] == ' ') continue;
if (std::isupper(str[i]))
tmp.push_back(std::tolower(str[i]));
else
tmp.push_back(str[i]);
}
return tmp;
}
void show_histogram(const pCaches& cc, std::string Cache::*ptr, const std::string& caption, bool html, bool sort_by_val) {
if (cc.empty()) return;
std::map<std::string, int> histogram;
for (auto el : cc)
histogram[el->*ptr]++;
show_histogram(histogram, caption, html, sort_by_val);
}
void show_histogram(const Caches& cc, std::string Cache::*ptr, const std::string& caption, bool html, bool sort_by_val) {
if (cc.empty()) return;
std::map<std::string, int> histogram;
for (const auto& el : cc)
histogram[el.*ptr]++;
show_histogram(histogram, caption, html, sort_by_val);
}
// void show_histogram(const pPowertrails& tt, const std::string& caption, bool html, bool sort_by_val) {
// if (tt.empty()) return;
//
// std::map<std::string, int> histogram;
//
// for (auto el : tt) {
// std::string link = "<a href=\"https://opencaching.pl/powerTrail.php?ptAction=showSerie&ptrail=" + std::to_string(el.second->number) + "\">" + el.second->name + "</a>";
// histogram[link] = 100 * el.second->found / el.second->caches.size();
// }
//
// show_histogram(histogram, caption, html, sort_by_val);
// }
void show_histogram(const std::map<std::string, int>& data, const std::string& caption, bool html, bool sort_by_val) {
if (data.empty()) return;
int HIST_MAX = 20;
std::vector<std::pair<std::string, int>> pairs;
if (!sort_by_val) HIST_MAX = 1000;
std::copy(data.begin(), data.end(), std::back_inserter(pairs));
if (sort_by_val)
sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second > b.second; });
else
sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.first < b.first; });
if (html) {
int max;
if (sort_by_val)
max = pairs[0].second;
else
max = std::max_element(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second < b.second; })->second;
int i = 0;
std::cout << "<h2>" << caption << "</h2>\n";
std::cout << "<div class=\"histogram " << string_mangle(caption) << "\">\n";
for (auto own : pairs) {
if (!own.first.starts_with("<a href=")) htmlencode(own.first);
if (own.first.empty()) own.first = "[unknown]";
if (i < HIST_MAX)
std::cout << "<div class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></div>\n";
else if (i == HIST_MAX) {
std::cout << "</div>\n";
std::cout << "<details class=\"histogram_others " << string_mangle(caption) << "\">\n<summary>See more</summary>\n<p>\n";
std::cout << own.first << " (" << own.second << ")<br>";
}
if (i > HIST_MAX)
std::cout << own.first << " (" << own.second << ")<br>";
i++;
}
if (i <= HIST_MAX)
std::cout << "</div>\n";
else
std::cout << "</p></details>\n";
} else {
std::cout << caption << '\n';
for (auto own : pairs)
std::cout << own.first << ": " << own.second << '\n';
}
}
void show_nested_histogram(const pCaches& fcc, std::string Cache::*ptr, std::string Cache::*ptr2, const std::string& caption, bool html, bool sort_by_val) {
if (fcc.empty()) return;
std::map<std::string, int> histogram;
std::vector<std::pair<std::string, int>> pairs;
for (auto el : fcc)
histogram[el->*ptr]++;
std::copy(histogram.begin(), histogram.end(), std::back_inserter(pairs));
if (sort_by_val)
sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second > b.second; });
else
sort(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.first < b.first; });
if (html) {
int HIST_MAX = 20;
int max;
if (sort_by_val)
max = pairs[0].second;
else
max = std::max_element(pairs.begin(), pairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second < b.second; })->second;
int i = 0;
std::cout << "<h2>" << caption << "</h2>\n";
std::cout << "<div class=\"histogram nested " << string_mangle(caption) << "\">\n";
for (auto own : pairs) {
htmlencode(own.first);
if (own.first.empty()) own.first = "[unknown]";
if (i < HIST_MAX) {
std::map<std::string, int> subhistogram;
for (auto el : fcc)
if (el->*ptr == own.first && !(el->*ptr2).empty()) //TODO problem with [unknown]
subhistogram[el->*ptr2]++;
if (!subhistogram.empty()) {
std::vector<std::pair<std::string, int>> subpairs;
std::cout << "<details class=\"nested\"><summary class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></summary>\n";
std::copy(subhistogram.begin(), subhistogram.end(), std::back_inserter(subpairs));
if (sort_by_val)
sort(subpairs.begin(), subpairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.second > b.second; });
else
sort(subpairs.begin(), subpairs.end(), [&](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) { return a.first < b.first; });
std::cout << "<div class=\"histogram inside\">\n";
for (auto own : subpairs) {
htmlencode(own.first);
if (own.first.empty()) own.first = "[unknown]";
std::cout << "<div class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></div>\n";
}
std::cout << "</div>\n";
std::cout << "</details>\n";
} else {
std::cout << "<div class=\"bar\" style=\"--percent: " << 100 * own.second / max << "%;\"><span class=\"text\">" << own.first << ": " << own.second << "</span></div>\n";
}
} else if (i == HIST_MAX) {
std::cout << "</div>\n";
std::cout << "<details class=\"histogram_others " << string_mangle(caption) << "\">\n<summary>See more</summary>\n<p>\n";
std::cout << own.first << " (" << own.second << ")";
}
if (i > HIST_MAX)
std::cout << ", " << own.first << " (" << own.second << ")";
i++;
}
if (i <= HIST_MAX)
std::cout << "</div>\n";
else
std::cout << "</p></details>\n";
} else { //TODO text-only version not implemented, displays only first-level list
std::cout << caption << '\n';
for (auto own : pairs)
std::cout << own.first << ": " << own.second << '\n';
}
}
uint find_streak(const Date_Caches& cc, std::time_t& start) {
int max_str = 0;
int cur_str = 0;
int cur = 0;
int cur_start = cur;
int max_start = cur;
for (auto i : cc) {
if (cur == i.first / 86400)
continue;
if (i.first / 86400 - cur == 1) {
cur_str++;
cur = i.first / 86400;
continue;
}
// streak ended
if (max_str < cur_str) {
max_str = cur_str;
max_start = cur_start;
}
cur_str = 1;
cur = cur_start = i.first / 86400;
if (max_str == 0) {
max_str = 1;
max_start = cur;
}
}
if (max_str < cur_str) {
max_str = cur_str;
max_start = cur_start;
}
start = max_start * 86400; // convert back from month count to time_t
return max_str;
}
uint get_num(char c, char* opt) {
try {
if (std::stoi(opt) > 0) {
return std::stoi(opt);
} else {
std::cout << "Option \"-" << c << "\" requires a valid number as an argument\n";
std::exit(EXIT_FAILURE);
}
}
catch (...) {
std::cout << "Option \"-" << c << "\" requires a valid number as an argument\n";
std::exit(EXIT_FAILURE);
}
}
template void average_html(const Caches& cc, float Cache::*ptr, const std::string& caption);
template void average_html(const Caches& cc, uint Cache::*ptr, const std::string& caption);
template void average_html(const Caches& cc, int Cache::*ptr, const std::string& caption);
template <typename T>
void average_html(const Caches& cc, T Cache::*ptr, const std::string& caption) {
std::cout << "<div class=\"basic_stats\">\n";
std::cout << "Average " << caption << ": <span class=\"value\">" << average(cc, ptr) << "</span><br>\n";
std::cout << "</div>\n";
}
template float average(const Caches& cc, float Cache::*ptr);
template float average(const Caches& cc, int Cache::*ptr);
template float average(const Caches& cc, uint Cache::*ptr);
template <typename T>
float average(const Caches& cc, T Cache::*ptr) {
return 1.0 * std::accumulate(cc.begin(), cc.end(), 0., [&](const float& a, const Cache& b) { return std::move(a) + b.*ptr; }) / cc.size();
}
template void sum_html(const Caches& cc, float Cache::*ptr, const std::string& caption);
template void sum_html(const Caches& cc, int Cache::*ptr, const std::string& caption);
template void sum_html(const Caches& cc, uint Cache::*ptr, const std::string& caption);
template <typename T>
void sum_html(const Caches& cc, T Cache::*ptr, const std::string& caption) {
std::cout << "<div class=\"basic_stats\">\n";
std::cout << "Total " << caption << ": <span class=\"value\">" << sum(cc, ptr) << "</span><br>\n";
std::cout << "</div>\n";
}
template float sum(const Caches& cc, float Cache::*ptr);
template int sum(const Caches& cc, int Cache::*ptr);
template uint sum(const Caches& cc, uint Cache::*ptr);
template <typename T>
T sum(const Caches& cc, T Cache::*ptr) {
return std::accumulate(cc.begin(), cc.end(), 0, [&](const int& a, const Cache& b) { return std::move(a) + b.*ptr; });
}
int count_caches(const Caches& cc, std::string& codes, std::function<bool(const Cache& c)> test) {
int count = 0;
codes.clear();
for (auto& c : cc) {
if (test(c)) {
count++;
codes += c.code;
codes += ' ';
}
}
codes.pop_back();
return count;
}
void header_html() {
std::cout << "<!DOCTYPE html>\n";
std::cout << "<html lang=\"en\">\n";
std::cout << " <head>\n";
std::cout << " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n";
std::cout << " <link rel=\"stylesheet\" type=\"text/css\" href=\"geo.css?ver=17\">\n";
std::cout << " <title>Geocaching stats</title>\n";
std::cout << "</head>\n";
std::cout << "<body>\n";
}
void footer_html() {
std::cout << "<footer>\n";
std::cout << "Stats generated by <a href=\"https://gitlab.com/tomaszg/geostat\">GeoStat</a>.<br>\n";
std::cout << "Data obtained from Opencaching sites via <a href=\"https://github.com/opencaching/okapi/\">OKAPI</a>:<br>\n";
std::cout << "<a href=\"https://www.opencaching.pl/\">Opencaching.pl</a>, ";
std::cout << "<a href=\"https://www.opencaching.de/\">Opencaching.de</a>, ";
std::cout << "<a href=\"http://www.opencaching.us/\">Opencaching.us</a>, ";
std::cout << "<a href=\"http://www.opencaching.nl/\">Opencaching.nl</a>, ";
std::cout << "<a href=\"http://www.opencaching.ro/\">Opencaching.ro</a>, ";
std::cout << "<a href=\"https://www.opencache.uk/\">Opencaching.uk</a>.";
std::cout << "</footer>\n";
std::cout << "</body>\n</html>\n";
}
Position Cache::home;
Position::Position(const std::string& loc) {
int pos = loc.find("|");
lat = stof(loc.substr(0, pos));
lon = stof(loc.substr(pos + 1));
}
float cache_distance(const Cache& a, const Cache& b) {
return 2 * Earth_radius * asin(sqrt(pow(sin(degtorad((a.pos.lat - b.pos.lat) / 2)), 2) + cos(degtorad(a.pos.lat)) * cos(degtorad(b.pos.lat)) * pow(sin(degtorad((a.pos.lon - b.pos.lon) / 2)), 2)));
}