#include "okapi.h" #include "gpx.h" #include "cache.h" #include "debug.h" #include "heat.h" #include "ocdb.h" #include "common.h" #include #include #include #include void show_usage() { std::cout << "Usage: geostat [options]\n"; std::cout << "Generate HTML stats from Opencaching data or GPX files.\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 << " * Output:\n"; std::cout << "\t-N\t\tcompute stats only for unfound caches (works only with SQLite)\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-m map\t\tchosen map: Poland, Poland_relief, Poland_big, Europe 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 use_oc = 0; bool use_ocpl_db = 0; bool get_not_found = 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 = "ocpl.sqlite"; std::string gpx_file; #include "config_user.h" if (argc == 1) show_usage(); int o; while ((o = getopt(argc, argv, "qNg:o::p:d:u:n:r:k:H:s:m:eh?")) != -1) switch (o) { // case 'd': // try { // if (std::stoi(optarg) > 0) { // // Debug(1) << "Setting debug level to " << optarg; // Debug::set_debug_level(std::stoi(optarg)); // } // } // catch (...) { // std::cout << "Option \"-d\" requires a valid number as an argument\n"; // std::exit(EXIT_FAILURE); // } // 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_ocpl_db = 1; break; case 'N': get_not_found = 1; break; case 'H': heat_file = optarg; break; case 's': try { if (std::stoi(optarg) > 0) { heat_stamp_size = std::stoi(optarg); } } catch (...) { std::cout << "Option \"-s\" requires a valid number as an argument\n"; std::exit(EXIT_FAILURE); } break; case 'm': heat_map = optarg; break; case 'e': heat_exp = 1; break; case 'h': case '?': default: show_usage(); } Caches cc; 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); cc.merge(OCpl.get_user_caches(ocpl_user_uuid, 0)); 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); cc.merge(OCde.get_user_caches(ocde_user_uuid, 0)); } 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); cc.merge(OCus.get_user_caches(ocus_user_uuid, 0)); } 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); cc.merge(OCnl.get_user_caches(ocnl_user_uuid, 0)); } 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); cc.merge(OCro.get_user_caches(ocro_user_uuid, 0)); } 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); cc.merge(OCuk.get_user_caches(ocuk_user_uuid, 0)); } } if (!gpx_file.empty()) { GPX gpxfile(gpx_file); cc.merge(gpxfile.get_user_caches()); } if (use_ocpl_db) { OCdb db(Database); Okapi OCpl(ocpl_url, ocpl_key); if (!ocpl_user.empty()) { ocpl_user_uuid = OCpl.get_uuid(ocpl_user); } if (get_not_found) { cc.merge(db.get_user_caches_not_found(ocpl_user_uuid)); } else { cc.merge(db.get_user_caches(ocpl_user_uuid, 0)); } } // 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 sorted list of caches, excluding moving caches std::map dates; Date_Caches sorted_caches; Caches fcc; std::tm tmp; for (auto& i : cc) { dates[i.date]++; if (i.type != "Moving" && i.type != "Own") { tmp = i.date_tm; sorted_caches.insert({ std::mktime(&tmp), &i }); fcc.insert(i); } } std::cout << "\n"; std::cout << "\n"; std::cout << " \n"; std::cout << " \n"; std::cout << " \n"; std::cout << " Geocaching stats\n"; std::cout << "\n"; std::cout << "\n"; std::cout << "

Geocaching stats for user " << ocpl_user << "

\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(fcc, chosen_map); hmap.generate(heat_file, heat_stamp_size, (heat_exp == 1 ? "exp" : "soft")); std::cout << "\"heat\n"; } if (!get_not_found) { std::cout << "
\n"; std::cout << "Number of caches found: " << cc.size() << "
\n"; auto best_day = std::max_element(dates.begin(), dates.end(), [&](auto& a, 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 << "Best caching day: " << best_day->first << ", found " << best_day->second << " caches\n"; std::cout << "\n \n \n
\n
\n"; for (auto& i : dates) std::cout << i.first << ", found " << i.second << " caches
\n"; std::cout << "
\n
\n
\n"; std::tm streak; int longest_str = find_streak(sorted_caches, streak); char str_tmp[20]; std::strftime(str_tmp, 20, "%F", &streak); std::cout << "Longest caching streak: " << longest_str << " starting on " << str_tmp << "
\n"; int tot_dist = 0; for (auto i = sorted_caches.begin(); i != std::prev(sorted_caches.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: " << tot_dist << " km (equivalent to " << tot_dist / (2 * Pi * Earth_radius) << "x trips around Earth)
\n"; std::cout << "Average distance between caches: " << tot_dist / sorted_caches.size() << " km
\n"; std::cout << "
\n"; } if (get_not_found) { std::cout << "
\n"; std::cout << "Still " << cc.size() << " caches to be found...
\n"; std::cout << "
\n"; } if (!get_not_found) { 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:link() << "\">" << N->name << " (" << N->code << ")" << N->pos.lat << "
South:link() << "\">" << S->name << " (" << S->code << ")" << S->pos.lat << "
East: link() << "\">" << E->name << " (" << E->code << ")" << E->pos.lon << "
West: link() << "\">" << W->name << " (" << W->code << ")" << W->pos.lon << "
\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::owner, "Cache owners", 1); show_histogram(&cc, &Cache::type, "Cache types", 1); show_histogram(&cc, &Cache::size, "Cache sizes", 1); show_histogram(&cc, &Cache::region, "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); short dt_table[11][11]; int 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 dt_table[i][j] = std::count_if(cc.begin(), cc.end(), [i, j](Cache c) { return (c.diff == i / 2.0 && c.terr == j / 2.0); }); if (dt_table[i][j] == 0) std::cout << ""; else { std::cout << ""; n++; } } std::cout << "\n"; } std::cout << "
" << j / 2.0 << "
" << i / 2.0 << "" << dt_table[i][j] << "" << dt_table[i][j] << "
\n"; std::cout << "
\n"; std::cout << "Total " << n << " combinations found out of 81 (" << std::setprecision(3) << n / 0.81 << "%).\n"; std::cout << "
\n"; } std::cout << "
\n"; std::cout << "Stats generated by GeoStat.
\n"; std::cout << "Data obtained from Opencaching sites via OKAPI:
\n"; std::cout << "Opencaching.pl, "; std::cout << "Opencaching.de, "; std::cout << "Opencaching.us, "; std::cout << "Opencaching.nl, "; std::cout << "Opencaching.ro, "; std::cout << "Opencaching.uk."; std::cout << "
\n"; std::cout << "\n\n"; }