#include "okapi.h" // #include "gpx.h" #include "cache.h" #include "debug.h" #include "heat.h" #include "ocdb.h" #include "common.h" #include "region.h" #include #include #include #include #include void show_usage() { std::cout << "Usage: geostat [options]\n"; std::cout << "Generate HTML stats from Opencaching data.\n\n"; std::cout << " * Data sources:\n"; std::cout << "\t-o[user]\tuse Opencaching; if specified user will be a user name used for all OC instances\n"; std::cout << "\t-p user\t\tuser for opencaching.pl\n"; std::cout << "\t-d user\t\tuser for opencaching.de\n"; std::cout << "\t-u user\t\tuser for opencaching.us\n"; std::cout << "\t-n user\t\tuser for opencaching.nl\n"; std::cout << "\t-r user\t\tuser for opencaching.ro\n"; std::cout << "\t-k user\t\tuser for opencaching.uk\n"; // std::cout << "\t-g file\t\tuse specified gpx file\n"; std::cout << "\t-q\t\tuse local SQLite file with dump of OC database\n"; std::cout << "\t-i timestamp\tstart date\n"; std::cout << "\t-f timestamp\tfinish date\n"; std::cout << " * Output:\n"; std::cout << "\t-N\t\tcompute stats for unfound caches (works only with SQLite)\n"; std::cout << "\t-O\t\tcompute stats for owned caches (works only with SQLite)\n"; std::cout << "\t-Q\t\texclude quiz caches from unfound caches\n"; std::cout << "\t-H file\t\trender a heat map to a file\n"; std::cout << "\t-s n\t\tstamp size for a heat map (default = 15)\n"; std::cout << "\t-e\t\tuse exponential to flatten the heat map\n"; std::cout << "\t-t\t\tdraw trail instead of heat map\n"; std::cout << "\t-a\t\tdraw animated map instead of heat map\n"; std::cout << "\t-m map\t\tchosen map: Poland, Poland_relief, Poland_big, Europe, World or a name of voivodeship (default = Poland)\n"; std::cout << "\t-h\t\tdisplay this help screen\n"; std::exit(EXIT_FAILURE); } int main(int argc, char** argv) { std::string heat_file; int heat_stamp_size = 15; std::string heat_map = "Poland"; bool heat_exp = 0; bool trail = 0; bool anim = 0; bool use_oc = 0; bool use_ocdb = 0; bool get_not_found = 0; bool get_owned = 0; bool exclude_quiz = 0; std::time_t start_time = 0; std::time_t end_time = std::time(nullptr); bool time_filter = 0; std::string ocpl_user; std::string ocde_user; std::string ocus_user; std::string ocnl_user; std::string ocro_user; std::string ocuk_user; std::string ocpl_user_profile; std::string ocde_user_profile; std::string ocus_user_profile; std::string ocnl_user_profile; std::string ocro_user_profile; std::string ocuk_user_profile; std::string ocpl_url = "https://opencaching.pl/okapi/"; std::string ocde_url = "https://www.opencaching.de/okapi/"; std::string ocus_url = "http://www.opencaching.us/okapi/"; std::string ocnl_url = "http://www.opencaching.nl/okapi/"; std::string ocro_url = "http://www.opencaching.ro/okapi/"; std::string ocuk_url = "https://opencache.uk/okapi/"; const std::string Database_pl = "ocpl.sqlite"; const std::string Database_de = "ocde.sqlite"; const std::string Database_us = "ocus.sqlite"; // std::string gpx_file; #include "config_user.h" if (argc == 1) show_usage(); int o; while ((o = getopt(argc, argv, "qNOQD:o::p:d:u:n:r:k:i:f:H:s:m:etah?")) != -1) switch (o) { case 'D': Debug::set_debug_level(get_num('d', optarg)); break; // case 'g': // gpx_file = optarg; // break; case 'o': use_oc = 1; if (optarg) { ocpl_user = optarg; ocde_user = optarg; ocus_user = optarg; ocnl_user = optarg; ocro_user = optarg; ocuk_user = optarg; } break; case 'p': ocpl_user = optarg; break; case 'd': ocde_user = optarg; break; case 'u': ocus_user = optarg; break; case 'n': ocnl_user = optarg; break; case 'r': ocro_user = optarg; break; case 'k': ocuk_user = optarg; break; case 'q': use_ocdb = 1; break; case 'i': start_time = get_num('i', optarg); time_filter = 1; break; case 'f': end_time = get_num('f', optarg); time_filter = 1; break; case 'N': get_not_found = 1; break; case 'O': get_owned = 1; break; case 'Q': exclude_quiz = 1; break; case 'H': heat_file = optarg; break; case 's': heat_stamp_size = get_num('s', optarg); break; case 'm': heat_map = optarg; break; case 'e': heat_exp = 1; break; case 't': trail = 1; break; case 'a': anim = 1; break; case 'h': case '?': default: show_usage(); } Caches cc; std::map region_count; if (get_not_found || get_owned) { use_oc = 0; use_ocdb = 1; trail = 0; if (ocpl_user_uuid.empty() && ocpl_user.empty() && ocde_user_uuid.empty() && ocde_user.empty()) { std::cout << "Options \"-N\" or \"-O\" work only with OCpl or OCde.\n"; std::exit(EXIT_FAILURE); } if (get_not_found && get_owned) { std::cout << "Options \"-N\" and \"-O\" are mutually exclusive.\n"; std::exit(EXIT_FAILURE); } } if (trail && anim) { std::cout << "Options \"-a\" and \"-t\" are mutually exclusive.\n"; std::exit(EXIT_FAILURE); } if (use_oc) { if (!ocpl_user_uuid.empty() || !ocpl_user.empty()) { Okapi OCpl(ocpl_url, ocpl_key); if (!ocpl_user.empty()) ocpl_user_uuid = OCpl.get_uuid(ocpl_user); Caches tmp = OCpl.get_user_caches(ocpl_user_uuid, 0); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); ocpl_user_profile = OCpl.get_profile_url(ocpl_user_uuid); } if (!ocde_user_uuid.empty() || !ocde_user.empty()) { Okapi OCde(ocde_url, ocde_key); if (!ocde_user.empty()) ocde_user_uuid = OCde.get_uuid(ocde_user); Caches tmp = OCde.get_user_caches(ocde_user_uuid, 0); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); ocde_user_profile = OCde.get_profile_url(ocde_user_uuid); } if (!ocus_user_uuid.empty() || !ocus_user.empty()) { Okapi OCus(ocus_url, ocus_key); if (!ocus_user.empty()) ocus_user_uuid = OCus.get_uuid(ocus_user); Caches tmp = OCus.get_user_caches(ocus_user_uuid, 0); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); ocus_user_profile = OCus.get_profile_url(ocus_user_uuid); } if (!ocnl_user_uuid.empty() || !ocnl_user.empty()) { Okapi OCnl(ocnl_url, ocnl_key); if (!ocnl_user.empty()) ocnl_user_uuid = OCnl.get_uuid(ocnl_user); Caches tmp = OCnl.get_user_caches(ocnl_user_uuid, 0); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); ocnl_user_profile = OCnl.get_profile_url(ocnl_user_uuid); } if (!ocro_user_uuid.empty() || !ocro_user.empty()) { Okapi OCro(ocro_url, ocro_key); if (!ocro_user.empty()) ocro_user_uuid = OCro.get_uuid(ocro_user); Caches tmp = OCro.get_user_caches(ocro_user_uuid, 0); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); ocro_user_profile = OCro.get_profile_url(ocro_user_uuid); } if (!ocuk_user_uuid.empty() || !ocuk_user.empty()) { Okapi OCuk(ocuk_url, ocuk_key); if (!ocuk_user.empty()) ocuk_user_uuid = OCuk.get_uuid(ocuk_user); Caches tmp = OCuk.get_user_caches(ocuk_user_uuid, 0); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); ocuk_user_profile = OCuk.get_profile_url(ocuk_user_uuid); } } // if (!gpx_file.empty()) { // GPX gpxfile(gpx_file); // Caches tmp = gpxfile.get_user_caches(); // std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); // } if (use_ocdb) { if (!ocpl_user_uuid.empty() || !ocpl_user.empty()) { OCdb db(Database_pl); if (!ocpl_user.empty()) { Okapi OCpl(ocpl_url, ocpl_key); ocpl_user_uuid = OCpl.get_uuid(ocpl_user); ocpl_user_profile = OCpl.get_profile_url(ocpl_user_uuid); } if (get_not_found) { Caches tmp = db.get_user_caches_not_found(ocpl_user_uuid); Okapi OCpl(ocpl_url, ocpl_key); OCpl.update_caches(tmp); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); region_count = db.get_region_stats(); } else if (get_owned) { Caches tmp = db.get_user_caches_owned(ocpl_user_uuid); Okapi OCpl(ocpl_url, ocpl_key); OCpl.update_caches(tmp); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); } else { Caches tmp = db.get_user_caches(ocpl_user_uuid, 0); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); } } if (!ocde_user_uuid.empty() || !ocde_user.empty()) { OCdb db(Database_de); if (!ocde_user.empty()) { Okapi OCde(ocde_url, ocde_key); ocde_user_uuid = OCde.get_uuid(ocde_user); ocde_user_profile = OCde.get_profile_url(ocde_user_uuid); } if (get_not_found) { Caches tmp = db.get_user_caches_not_found(ocde_user_uuid); Okapi OCde(ocde_url, ocde_key); OCde.update_caches(tmp); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); // region_count = db.get_region_stats(); } else if (get_owned) { Caches tmp = db.get_user_caches_owned(ocde_user_uuid); Okapi OCde(ocde_url, ocde_key); OCde.update_caches(tmp); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); } else { Caches tmp = db.get_user_caches(ocde_user_uuid, 0); std::copy(tmp.begin(), tmp.end(), std::back_inserter(cc)); } } } // TODO: some cache deduplication is needed Debug(2) << "Caches read: " << cc.size() << '\n'; if (cc.size() == 0) { std::cout << "No caches found, aborting.\n"; std::exit(EXIT_FAILURE); } // Prepare a set of regions Country poland({ "dolnoslaskie", "gornoslaskie", "kujawsko-pomorskie", "lodzkie", "lubelskie", "lubuskie", "malopolskie", "mazowieckie", "opolskie", "podkarpackie", "podlaskie", "pomorskie", "swietokrzyskie", "warminsko-mazurskie", "wielkopolskie", "zachodniopomorskie" }); // Prepare sorted list of caches, excluding moving caches std::map dates; Date_Caches sorted_caches; Date_Caches sorted_fcaches; Date_Caches sorted_caches_by_hidden; pCaches caches_by_fav; pCaches caches_by_fav_perc; pCaches caches_by_finds; // pCaches caches_by_rating; pCaches fcc; if (!get_not_found) { for (auto i = cc.begin(); i != cc.end();) { if (time_filter && (i->date_t > end_time || i->date_t < start_time)) { i = cc.erase(i); continue; } poland.locate(*i); dates[i->date]++; sorted_caches.insert({ i->date_t, &*i }); if ((i->type != "Moving" && i->type != "Own") || get_owned) { sorted_fcaches.insert({ i->date_t, &*i }); fcc.push_back(&*i); } sorted_caches_by_hidden.insert({ i->date_hidden_t, &*i }); caches_by_fav.push_back(&*i); caches_by_fav_perc.push_back(&*i); caches_by_finds.push_back(&*i); // caches_by_rating.push_back(&*i); i++; } std::sort(caches_by_fav.begin(), caches_by_fav.end(), [&](const Cache* a, const Cache* b) { return a->fav > b->fav; }); std::sort(caches_by_fav_perc.begin(), caches_by_fav_perc.end(), [&](const Cache* a, const Cache* b) { return 1.0 * a->fav / a->founds > 1.0 * b->fav / b->founds; }); std::sort(caches_by_finds.begin(), caches_by_finds.end(), [&](const Cache* a, const Cache* b) { return a->founds > b->founds; }); // std::sort(caches_by_rating.begin(), caches_by_rating.end(), [&](const Cache* a, const Cache* b) { return a->rating > b->rating; }); } else { for (auto& i : cc) { if (i.type != "Moving" && i.type != "Own" && (!exclude_quiz || i.type != "Quiz")) { fcc.push_back(&i); poland.locate(i); } } } header_html(); std::cout << "
\n"; std::cout << "

Geocaching stats for user profiles:

\n"; if (!ocpl_user.empty()) std::cout << "\"OCpl\" " << ocpl_user << "
\n"; if (!ocde_user.empty()) std::cout << "\"OCde\" " << ocde_user << "
\n"; if (!ocus_user.empty()) std::cout << "\"OCna\" " << ocus_user << "
\n"; if (!ocnl_user.empty()) std::cout << "\"OCnl\" " << ocnl_user << "
\n"; if (!ocro_user.empty()) std::cout << "\"OCro\" " << ocro_user << "
\n"; if (!ocuk_user.empty()) std::cout << "\"OCuk\" " << ocuk_user << "
\n"; if (time_filter) { std::cout << "for time range " << std::put_time(std::localtime(&start_time), "%x") << "—" << std::put_time(std::localtime(&end_time), "%x"); } std::cout << "
\n"; if (!heat_file.empty()) { const Map* chosen_map; if (maps.count(heat_map) > 0) chosen_map = &maps.at(heat_map); else { std::cout << "Map " << heat_map << " not found.\n"; std::exit(EXIT_FAILURE); } Heat hmap(chosen_map); if (trail) { if (sorted_fcaches.size() < 2) { std::cout << "You need at least 2 caches for a trail.\n"; std::exit(EXIT_FAILURE); } hmap.generate_path(heat_file, sorted_fcaches); } else if (anim) hmap.generate_anim(heat_file, sorted_fcaches, heat_stamp_size); else hmap.generate(heat_file, fcc, heat_stamp_size, (heat_exp == 1 ? "exp" : "soft")); if (!anim) std::cout << "
\n\"heat\n
\n"; else { std::cout << "\n"; std::cout << "
\n\n
\n"; std::cout << "\n"; } } if (!get_not_found && !get_owned) { short count; std::string codes; int n; std::cout << "
\n"; std::cout << "Number of caches found: " << cc.size() << "
\n"; auto best_day = std::max_element(dates.begin(), dates.end(), [&](const auto& a, const auto& b) { return a.second < b.second; }); std::cout << "Number of caching days: " << dates.size() << "
\n"; std::cout << "Average caches per caching day: " << std::setprecision(3) << (1.0 * cc.size()) / dates.size() << "
\n"; std::cout << std::resetiosflags(std::cout.flags()); std::cout << "Number of caches that are now archived: " << std::count_if(cc.begin(), cc.end(), [&](const auto& a) { return a.status == archived; }) << "
\n"; std::cout << "
Best caching day: " << best_day->first << ", found " << best_day->second << " caches\n"; std::cout << "

\n"; for (auto& i : dates) std::cout << i.first << ", found " << i.second << " caches
\n"; std::cout << "

\n
\n"; std::time_t streak; int longest_str = find_streak(sorted_caches, streak); char str_tmp[20]; std::tm* str_tm = std::localtime(&streak); std::strftime(str_tmp, 20, "%F", str_tm); std::cout << "Longest caching streak: " << longest_str << " starting on " << str_tmp << "
\n"; float tot_dist = 0; for (auto i = sorted_fcaches.begin(); i != std::prev(sorted_fcaches.end()); i++) { //std::cout << "Distance between " << i->second->name << " and " << std::next(i)->second->name << " is " << cache_distance(*i->second, *std::next(i)->second) << "
"; tot_dist += cache_distance(*i->second, *std::next(i)->second); } std::cout << "Total distance between caches: " << std::fixed << tot_dist << " km (equivalent to " << tot_dist / (2 * M_PI * Earth_radius) << "x trips around Earth or " << tot_dist / Moon_dist << "x trips to the Moon)
\n"; std::cout << std::resetiosflags(std::cout.flags()); if (sorted_fcaches.size() > 1) std::cout << "Average distance between caches: " << tot_dist / (sorted_fcaches.size() - 1) << " km
\n"; std::cout << "
\n"; short y_min = std::min_element(cc.begin(), cc.end(), [&](const Cache& a, const Cache& b) { return a.date_tm.tm_year < b.date_tm.tm_year; })->date_tm.tm_year; short y_max = std::max_element(cc.begin(), cc.end(), [&](const Cache& a, const Cache& b) { return a.date_tm.tm_year < b.date_tm.tm_year; })->date_tm.tm_year; long max = 0; // maximal value for histogram for (int i = y_min; i <= y_max; i++) for (int j = 1; j <= 12; j++) max = std::max(max, std::count_if(cc.begin(), cc.end(), [i, j](Cache c) { return (c.date_tm.tm_year == i && c.date_tm.tm_mon == j - 1); })); if (max > 0) { std::cout << "

Caching timeline

\n"; std::cout << "\n"; std::cout << ""; for (int j = 1; j <= 12; j++) { // print table month headers std::cout << ""; } std::cout << "\n"; for (int i = y_min; i <= y_max; i++) { // i -> years in rows std::cout << " "; for (int j = 1; j <= 12; j++) { // j -> months in cols count = std::count_if(cc.begin(), cc.end(), [i, j](Cache c) { return (c.date_tm.tm_year == i && c.date_tm.tm_mon == j - 1); }); std::cout << ""; } std::cout << "\n"; } std::cout << "
" << j << "
" << i + 1900 << "" << count << "
\n"; } std::cout << "

Caching days per month

\n"; std::cout << "\n"; std::cout << ""; for (int j = 1; j <= 12; j++) { // print table month headers std::cout << ""; } std::cout << "\n"; max = 31; // maximal value for histogram for (int i = y_min; i <= y_max; i++) { // i -> years in rows std::cout << " "; for (int j = 1; j <= 12; j++) { // j -> months in cols std::map days_count; for (auto el : cc) { if (el.date_tm.tm_mon == j - 1 && el.date_tm.tm_year == i) days_count[el.date_tm.tm_mday]++; } std::cout << ""; } std::cout << "\n"; } std::cout << "
" << j << "
" << i + 1900 << "" << days_count.size() << "
\n"; // auto far = std::max_element(fcc.begin(), fcc.end(), [&](const Cache& a, const Cache& b) { return a.distance() < b.distance(); }); // auto near = std::min_element(fcc.begin(), fcc.end(), [&](const Cache& a, const Cache& b) { return a.distance() < b.distance(); }); // // std::cout << "Nearest cache: " << near->distance() << " km\n"; // near->show(); // std::cout << "Furthest cache: " << far->distance() << " km\n"; // far->show(); // show_histogram(cc, &Cache::year_month, "Caching timeline", 1, 0); show_histogram(cc, &Cache::owner, "Cache owners", 1); show_histogram(cc, &Cache::type, "Cache types", 1); show_histogram(cc, &Cache::size, "Cache sizes", 1); // show_histogram(fcc, &Cache::region, "Regions", 1); // show_histogram(fcc, &Cache::subregion, "Subregions", 1); show_histogram(fcc, &Cache::country, "Countries", 1); show_nested_histogram(fcc, &Cache::region, &Cache::subregion, "Regions", 1); show_histogram(cc, &Cache::day_of_week, "Days of the week", 1, 0); show_histogram(cc, &Cache::mon, "Months", 1, 0); show_histogram(cc, &Cache::year, "Years", 1, 0); auto N = *std::max_element(fcc.begin(), fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lat < b->pos.lat; }); auto S = *std::min_element(fcc.begin(), fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lat < b->pos.lat; }); auto E = *std::max_element(fcc.begin(), fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lon < b->pos.lon; }); auto W = *std::min_element(fcc.begin(), fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lon < b->pos.lon; }); std::cout << "

Geographically extreme caches

\n"; std::cout << "\n"; std::cout << "\n"; std::cout << " \n"; std::cout << "\n"; std::cout << " \n"; std::cout << "\n"; std::cout << " \n"; std::cout << "\n"; std::cout << " \n"; std::cout << "\n"; std::cout << "
North:" << N->link_name() << "" << N->region << "" << N->pos.lat << "
South:" << S->link_name() << "" << S->region << "" << S->pos.lat << "
East: " << E->link_name() << "" << E->region << "" << E->pos.lon << "
West: " << W->link_name() << "" << W->region << "" << W->pos.lon << "
\n"; const int LIST_MAX = 15; n = 1; std::cout << "

Oldest caches found

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i = sorted_caches_by_hidden.begin(); i != sorted_caches_by_hidden.end(); i++) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeRegionDate hiddenDate found
" << n << "" << i->second->link_name() << "" << i->second->type << "" << i->second->region << "" << i->second->date_hidden << "" << i->second->date << "
\n"; n = 1; std::cout << "

Newest caches found

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i = sorted_caches_by_hidden.rbegin(); i != sorted_caches_by_hidden.rend(); i++) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeRegionDate hiddenDate found
" << n << "" << i->second->link_name() << "" << i->second->type << "" << i->second->region << "" << i->second->date_hidden << "" << i->second->date << "
\n"; std::cout << "
\n"; std::cout << "Average age of cache at the moment of finding: " << average(cc, &Cache::age_when_found) / 365 * 12 << " months
\n"; std::cout << "Number of caches found within first 24h: " << std::count_if(cc.begin(), cc.end(), [&](const auto& a) { return a.age_when_found == 0; }) << "
\n"; std::cout << "Number of caches found within first 48h: " << std::count_if(cc.begin(), cc.end(), [&](const auto& a) { return a.age_when_found <= 1; }) << "
\n"; std::cout << "Number of caches found during first week: " << std::count_if(cc.begin(), cc.end(), [&](const auto& a) { return a.age_when_found < 7; }) << "
\n"; std::cout << "Number of caches found during first month: " << std::count_if(cc.begin(), cc.end(), [&](const auto& a) { return a.age_when_found < 30; }) << "
\n"; std::cout << "Number of caches found after more than 5 years: " << std::count_if(cc.begin(), cc.end(), [&](const auto& a) { return a.age_when_found > 365 * 5; }) << "
\n"; std::cout << "Number of caches found after more than 10 years: " << std::count_if(cc.begin(), cc.end(), [&](const auto& a) { return a.age_when_found > 365 * 10; }) << "
\n"; std::cout << "
\n"; n = 1; std::cout << "

Caches with most recommendations

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i : caches_by_fav) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeRegionRec.Finds%
" << n << "" << i->link_name() << "" << i->type << "" << i->region << "" << i->fav << "" << i->founds << "" << std::setprecision(3) << 100.0 * i->fav / i->founds << "%
\n"; average_html(cc, &Cache::fav, "number of recommendations"); n = 1; std::cout << "

Caches with most recommendations (percentage)

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i : caches_by_fav_perc) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeRegionRec.Finds%
" << n << "" << i->link_name() << "" << i->type << "" << i->region << "" << i->fav << "" << i->founds << "" << std::setprecision(3) << 100.0 * i->fav / i->founds << "%
\n"; /* n = 1; std::cout << "

Caches with highest rating

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i : caches_by_rating) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeRegionRatingFinds
" << n << "" << i->link_name() << "" << i->type << "" << i->region << "" << i->rating << "" << i->founds << "
\n"; average_html(cc, &Cache::rating, "rating"); */ n = 1; std::cout << "

Caches with most finds

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i : caches_by_finds) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeRegionDate hiddenFinds
" << n << "" << i->link_name() << "" << i->type << "" << i->region << "" << i->date_hidden << "" << i->founds << "
\n"; average_html(cc, &Cache::founds, "number of finds"); n = 1; std::cout << "

Caches with least finds

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i = caches_by_finds.rbegin(); i != caches_by_finds.rend(); i++) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeRegionDate hiddenFinds
" << n << "" << (*i)->link_name() << "" << (*i)->type << "" << (*i)->region << "" << (*i)->date_hidden << "" << (*i)->founds << "
\n"; // D/T matrix n = 0; std::cout << "

Difficulty / terrain matrix

\n"; std::cout << "\n"; std::cout << ""; for (int j = 2; j <= 10; j++) { // print table terr headers std::cout << ""; } std::cout << "\n"; for (int i = 2; i <= 10; i++) { // i -> diff in rows std::cout << " "; for (int j = 2; j <= 10; j++) { // j -> terr in cols count = count_caches(cc, codes, [i, j](const Cache& c) -> bool { return (c.diff == i / 2.0 && c.terr == j / 2.0); }); if (count == 0) std::cout << ""; else { std::cout << ""; n++; } } std::cout << "\n"; } std::cout << "
" << j / 2.0 << "
" << i / 2.0 << "" << 0 << "" << count << "
\n"; std::cout << "
\n"; std::cout << "Total " << n << " combinations found out of 81 (" << std::setprecision(3) << n / 0.81 << "%).
\n"; std::cout << "Average difficulty: " << average(cc, &Cache::diff) << "
\n"; std::cout << "Average terrain: " << average(cc, &Cache::terr) << "
\n"; std::cout << "
\n"; // Days matrix n = 0; std::cout << "

Caching days matrix

\n"; std::cout << "\n"; std::cout << ""; for (int j = 1; j <= 31; j++) { // print table day headers std::cout << ""; } std::cout << "\n"; for (int i = 1; i <= 12; i++) { // i -> months in rows int m = 0; std::cout << " "; for (int j = 1; j <= 31; j++) { // j -> days in cols count = count_caches(cc, codes, [i, j](const Cache& c) -> bool { return (c.date_tm.tm_mon == i - 1 && c.date_tm.tm_mday == j); }); if (count == 0) { if ((j == 31 && (i == 2 || i == 4 || i == 6 || i == 9 || i == 11)) || (j == 30 && i == 2)) std::cout << ""; else std::cout << ""; } else { std::cout << ""; n++; m++; } } float perc; switch(i) { case 4: case 6: case 9: case 11: perc = m * 100.0 / 30; break; case 2: perc = m * 100.0 / 29; break; default: perc = m * 100.0 / 31; } std::cout << ""; std::cout << "\n"; } std::cout << "
" << j << "
" << i << "" << 0 << "" << count << "" << perc << "%
\n"; std::cout << "
\n"; std::cout << "Total " << n << " days out of 366 (" << std::setprecision(3) << n / 3.66 << "%).\n"; std::cout << "
\n"; // Hidden months matrix n = 0; y_min = std::min_element(cc.begin(), cc.end(), [&](const Cache& a, const Cache& b) { return a.date_hidden_tm.tm_year < b.date_hidden_tm.tm_year; })->date_hidden_tm.tm_year; y_max = std::max_element(cc.begin(), cc.end(), [&](const Cache& a, const Cache& b) { return a.date_hidden_tm.tm_year < b.date_hidden_tm.tm_year; })->date_hidden_tm.tm_year; std::cout << "

Caches by hidden month matrix

\n"; std::cout << "\n"; std::cout << ""; for (int j = 1; j <= 12; j++) { // print table month headers std::cout << ""; } std::cout << "\n"; std::time_t now = std::time(nullptr); std::tm* now_tm = std::localtime(&now); for (int i = y_min; i <= y_max; i++) { // i -> years in rows int m = 0; std::cout << " "; for (int j = 1; j <= 12; j++) { // j -> months in cols count = count_caches(cc, codes, [i, j](const Cache& c) -> bool { return (c.date_hidden_tm.tm_year == i && c.date_hidden_tm.tm_mon == j - 1); }); if (count == 0) { if ((!ocpl_user_uuid.empty() && ocde_user_uuid.empty() && i + 1900 == 2006 && j < 5) || (!ocde_user_uuid.empty() && i + 1900 == 2005 && j < 8)) std::cout << ""; else std::cout << ""; } else { std::cout << ""; n++; m++; } } if (now_tm) { if (i == now_tm->tm_year) std::cout << ""; else std::cout << ""; } std::cout << "\n"; } std::cout << "
" << j << "
" << i + 1900 << "" << 0 << "" << count << "" << m * 100.0 / (now_tm->tm_mon + 1) << "%" << m * 100.0 / 12 << "%
\n"; if (now_tm) { if (!ocpl_user_uuid.empty() && ocde_user_uuid.empty()) { int m_count = (now_tm->tm_year - 106 - 1) * 12 + 8 + now_tm->tm_mon + 1; // 106 corresponds to 2006, 8 is no. of months since may till dec, +1 since tm_mon starts at 0 std::cout << "
\n"; std::cout << "Total " << n << " months out of " << m_count << " (" << std::setprecision(3) << n * 100.0 / m_count << "%).\n"; std::cout << "
\n"; } else if (!ocde_user_uuid.empty()) { int m_count = (now_tm->tm_year - 105 - 1) * 12 + 5 + now_tm->tm_mon + 1; // 106 corresponds to 2005, 8 is no. of months since aug till dec, +1 since tm_mon starts at 0 std::cout << "
\n"; std::cout << "Total " << n << " months out of " << m_count << " (" << std::setprecision(3) << n * 100.0 / m_count << "%).\n"; std::cout << "
\n"; } } } // end of main if if (get_not_found) { std::cout << "
\n"; std::cout << "Still " << cc.size() << " caches to be found...
\n"; std::cout << "
\n"; show_nested_histogram(fcc, &Cache::region, &Cache::subregion, "Regions", 1); std::map region_found_count; for (auto el : fcc) region_found_count[el->region]++; for (auto& i : region_count) if (i.second != 0) i.second = region_found_count[i.first] * 100 / i.second; show_histogram(region_count, "Percentage", 1); } // end of "not found" if if (get_owned) { short count; std::string codes; int n; std::cout << "
\n"; std::cout << "Number of caches created: " << cc.size() << "
\n"; std::cout << "Number of caches that are disabled: " << std::count_if(cc.begin(), cc.end(), [&](const auto& a) { return a.status == disabled; }) << "
\n"; std::cout << "Number of caches that are archived: " << std::count_if(cc.begin(), cc.end(), [&](const auto& a) { return a.status == archived; }) << "
\n"; std::cout << "Average finds per cache per day: " << std::setprecision(3) << std::accumulate(cc.begin(), cc.end(), 0., [&](const float& a, const Cache& b) { return std::move(a) + 1.0 * b.founds / std::max(b.age_now, 1); }) / cc.size() << "
\n"; std::cout << std::resetiosflags(std::cout.flags()); std::cout << "
\n"; short y_min = std::min_element(cc.begin(), cc.end(), [&](const Cache& a, const Cache& b) { return a.date_hidden_tm.tm_year < b.date_hidden_tm.tm_year; })->date_hidden_tm.tm_year; short y_max = std::max_element(cc.begin(), cc.end(), [&](const Cache& a, const Cache& b) { return a.date_hidden_tm.tm_year < b.date_hidden_tm.tm_year; })->date_hidden_tm.tm_year; long max = 0; // maximal value for histogram for (int i = y_min; i <= y_max; i++) for (int j = 1; j <= 12; j++) max = std::max(max, std::count_if(cc.begin(), cc.end(), [i, j](Cache c) { return (c.date_hidden_tm.tm_year == i && c.date_hidden_tm.tm_mon == j - 1); })); if (max > 0) { std::cout << "

Cache creation timeline

\n"; std::cout << "\n"; std::cout << ""; for (int j = 1; j <= 12; j++) { // print table month headers std::cout << ""; } std::cout << "\n"; for (int i = y_min; i <= y_max; i++) { // i -> years in rows std::cout << " "; for (int j = 1; j <= 12; j++) { // j -> months in cols count = std::count_if(cc.begin(), cc.end(), [i, j](Cache c) { return (c.date_hidden_tm.tm_year == i && c.date_hidden_tm.tm_mon == j - 1); }); std::cout << ""; } std::cout << "\n"; } std::cout << "
" << j << "
" << i + 1900 << "" << count << "
\n"; } // auto far = std::max_element(fcc.begin(), fcc.end(), [&](const Cache& a, const Cache& b) { return a.distance() < b.distance(); }); // auto near = std::min_element(fcc.begin(), fcc.end(), [&](const Cache& a, const Cache& b) { return a.distance() < b.distance(); }); // // std::cout << "Nearest cache: " << near->distance() << " km\n"; // near->show(); // std::cout << "Furthest cache: " << far->distance() << " km\n"; // far->show(); show_histogram(cc, &Cache::type, "Cache types", 1); show_histogram(cc, &Cache::size, "Cache sizes", 1); show_nested_histogram(fcc, &Cache::region, &Cache::subregion, "Regions", 1); auto N = *std::max_element(fcc.begin(), fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lat < b->pos.lat; }); auto S = *std::min_element(fcc.begin(), fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lat < b->pos.lat; }); auto E = *std::max_element(fcc.begin(), fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lon < b->pos.lon; }); auto W = *std::min_element(fcc.begin(), fcc.end(), [&](const Cache* a, const Cache* b) { return a->pos.lon < b->pos.lon; }); std::cout << "

Geographically extreme caches

\n"; std::cout << "\n"; std::cout << "\n"; std::cout << " \n"; std::cout << "\n"; std::cout << " \n"; std::cout << "\n"; std::cout << " \n"; std::cout << "\n"; std::cout << " \n"; std::cout << "\n"; std::cout << "
North:" << N->link_name() << "" << N->pos.lat << "
South:" << S->link_name() << "" << S->pos.lat << "
East: " << E->link_name() << "" << E->pos.lon << "
West: " << W->link_name() << "" << W->pos.lon << "
\n"; const int LIST_MAX = 15; n = 1; std::cout << "

Caches with most recommendations

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i : caches_by_fav) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; if (i->founds > 0) std::cout << ""; else std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeRec.Finds%
" << n << "" << i->link_name() << "" << i->type << "" << i->fav << "" << i->founds << "" << std::setprecision(3) << 100.0 * i->fav / i->founds << "%
\n"; average_html(cc, &Cache::fav, "number of recommendations"); sum_html(cc, &Cache::fav, "number of recommendations"); n = 1; std::cout << "

Caches with most recommendations (percentage)

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i : caches_by_fav_perc) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; if (i->founds > 0) std::cout << ""; else std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeRec.Finds%
" << n << "" << i->link_name() << "" << i->type << "" << i->fav << "" << i->founds << "" << std::setprecision(3) << 100.0 * i->fav / i->founds << "%
\n"; /* n = 1; std::cout << "

Caches with highest rating

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i : caches_by_rating) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeRatingFinds
" << n << "" << i->link_name() << "" << i->type << "" << i->rating << "" << i->founds << "
\n"; average_html(cc, &Cache::rating, "rating"); */ n = 1; std::cout << "

Caches with most finds

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i : caches_by_finds) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeDate hiddenFinds
" << n << "" << i->link_name() << "" << i->type << "" << i->date_hidden << "" << i->founds << "
\n"; average_html(cc, &Cache::founds, "number of finds"); sum_html(cc, &Cache::founds, "number of finds"); n = 1; std::cout << "

Caches with least finds

\n"; std::cout << "\n"; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; for (auto i = caches_by_finds.rbegin(); i != caches_by_finds.rend(); i++) { std::cout << " "; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << ""; std::cout << "\n"; n++; if (n > LIST_MAX) break; } std::cout << "
CacheTypeDate hiddenFinds
" << n << "" << (*i)->link_name() << "" << (*i)->type << "" << (*i)->date_hidden << "" << (*i)->founds << "
\n"; // D/T matrix n = 0; std::cout << "

Difficulty / terrain matrix

\n"; std::cout << "\n"; std::cout << ""; for (int j = 2; j <= 10; j++) { // print table terr headers std::cout << ""; } std::cout << "\n"; for (int i = 2; i <= 10; i++) { // i -> diff in rows std::cout << " "; for (int j = 2; j <= 10; j++) { // j -> terr in cols count = count_caches(cc, codes, [i, j](const Cache& c) -> bool { return (c.diff == i / 2.0 && c.terr == j / 2.0); }); if (count == 0) std::cout << ""; else { std::cout << ""; n++; } } std::cout << "\n"; } std::cout << "
" << j / 2.0 << "
" << i / 2.0 << "" << 0 << "" << count << "
\n"; std::cout << "
\n"; std::cout << "Total " << n << " combinations created out of 81 (" << std::setprecision(3) << n / 0.81 << "%).
\n"; std::cout << "Average difficulty: " << average(cc, &Cache::diff) << "
\n"; std::cout << "Average terrain: " << average(cc, &Cache::terr) << "
\n"; std::cout << "
\n"; } // end of "owned" if footer_html(); }