commit e2b3e1ebc15a14954343c9627ab31d54b0e02287 Author: eriedaberrie Date: Mon Jan 8 17:14:18 2024 -0800 Initial commit diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26baaca --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.cache/ +.direnv/ +build*/ +result* + +/out/ +/fires/ + +*.csv + +*.sb[nx] +*.sh[px] +*.shp.xml +*.cpg +*.dbf +*.prj diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..c8bfd36 --- /dev/null +++ b/default.nix @@ -0,0 +1,20 @@ +{ + lib, + stdenv, + meson, + pkg-config, + ninja, + geographiclib, + shapelib, + strip ? false, +}: +stdenv.mkDerivation { + name = "shapething"; + src = lib.cleanSource ./.; + + nativeBuildInputs = [meson pkg-config ninja]; + buildInputs = [geographiclib shapelib]; + + mesonBuildType = "release"; + mesonFlags = lib.optional strip "--strip"; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..152ad7e --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1704194953, + "narHash": "sha256-RtDKd8Mynhe5CFnVT8s0/0yqtWFMM9LmCzXv/YKxnq4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "bd645e8668ec6612439a9ee7e71f7eac4099d4f6", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..01bdc1f --- /dev/null +++ b/flake.nix @@ -0,0 +1,42 @@ +{ + inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; + + outputs = { + self, + nixpkgs, + }: let + inherit (nixpkgs) lib; + genSystems = lib.genAttrs [ + "x86_64-linux" + "aarch64-linux" + ]; + pkgsFor = system: import nixpkgs {inherit system;}; + in { + packages = genSystems ( + system: let + pkgs = pkgsFor system; + in { + default = pkgs.callPackage ./. {}; + static = pkgs.pkgsStatic.callPackage ./. { + strip = true; + }; + } + ); + + devShells = genSystems ( + system: let + pkgs = pkgsFor system; + in { + default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [clang-tools]; + inputsFrom = [self.packages.${system}.default]; + }; + pyShell = pkgs.mkShell { + nativeBuildInputs = [(pkgs.python3.withPackages (ps: with ps; [geopandas]))]; + }; + } + ); + + formatter = genSystems (system: (pkgsFor system).alejandra); + }; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..17dabec --- /dev/null +++ b/meson.build @@ -0,0 +1,17 @@ +project('shapefile-test', 'cpp', + default_options : [ + 'cpp_std=c++20', + 'warning_level=3', + 'buildtype=release', + ] +) + +executable( + 'shapething', + 'src/main.cpp', + dependencies : [ + dependency('geographiclib'), + dependency('shapelib'), + ], + install : true +) diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1aaaf43 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct month_info { + unsigned long long n_fires; + std::string name; + std::map>> days; +}; + +int main(int argc, char *argv[]) { + unsigned int distance = 2'000; + std::string fire_dir = "fires"; + std::filesystem::path out_dir = "out"; + auto do_sort = false; + int c; + while ((c = getopt(argc, argv, "d:f:o:s")) != -1) { + switch (c) { + case 'd': + distance = atoi(optarg); + break; + case 'f': + fire_dir = optarg; + break; + case 'o': + out_dir = optarg; + break; + case 's': + do_sort = true; + break; + case '?': + switch (optopt) { + case 'd': + std::cerr << "Option -d requires a distance argument" << std::endl; + break; + default: + std::cerr << "Unknown option " << optopt << std::endl; + } + return 1; + } + } + + char *res_loc; + switch (argc - optind) { + case 0: { + res_loc = NULL; + for (const auto &FE : std::filesystem::directory_iterator(".")) { + if (!FE.is_regular_file()) + continue; + + const auto FP = FE.path(); + if (FP.extension() != ".shp") + continue; + + if (res_loc != NULL) { + free(res_loc); + res_loc = NULL; + break; + } + + const std::string FPS = FP; + res_loc = static_cast(malloc((FPS.size() + 1) * sizeof(*res_loc))); + FPS.copy(res_loc, FPS.size()); + res_loc[FPS.size()] = '\0'; + } + if (res_loc == NULL) { + std::cerr << "Failed to automatically find residents shapefile!" << std::endl; + return 1; + } + break; + } + case 1: + res_loc = argv[optind]; + break; + default: + std::cerr << "Too many arguments!" << std::endl; + return 1; + } + + const auto RES_H = SHPOpen(res_loc, "rb"); + + std::cout << "Reading residents data..." << std::endl; + int res_n, res_type; + SHPGetInfo(RES_H, &res_n, &res_type, NULL, NULL); + if (res_type != SHPT_POINT) { + std::cerr << "Expected point data from " << res_loc << "!" << std::endl; + return 1; + } + + std::vector> residentials; + for (auto i = 0; i < res_n; i++) { + const auto RES_P = SHPReadObject(RES_H, i); + residentials.push_back(std::make_pair(RES_P->padfX[0], RES_P->padfY[0])); + SHPDestroyObject(RES_P); + } + + SHPClose(RES_H); + + + std::cout << "Reading fires data..." << std::endl; + std::vector all_fires; + for (const auto& MONTH_ENTRY : std::filesystem::directory_iterator(fire_dir)) { + if (!std::filesystem::is_directory(MONTH_ENTRY)) + continue; + + const std::string MONTH = MONTH_ENTRY.path().filename(); + std::map>> all_days; + unsigned long long n_fires_month = 0; + + for (const auto& DAY_ENTRY : std::filesystem::directory_iterator(MONTH_ENTRY)) { + const auto FP = DAY_ENTRY.path(); + if (FP.extension() != ".shp") + continue; + + const auto DAY = FP.stem().string().substr(8); + + const auto FIRE_H = SHPOpen(FP.c_str(), "rb"); + int fire_n, fire_type; + SHPGetInfo(FIRE_H, &fire_n, &fire_type, NULL, NULL); + if (res_type != SHPT_POINT) { + std::cerr << "Expected point data from " << FP << "!" << std::endl; + return 1; + } + + std::vector> fires; + for (auto i = 0; i < fire_n; i++) { + const auto FIRE_P = SHPReadObject(FIRE_H, i); + int zone; + bool northp; + double x, y; + GeographicLib::UTMUPS::Forward(FIRE_P->padfY[0], FIRE_P->padfX[0], zone, northp, x, y); + fires.push_back(std::make_pair(x, y)); + SHPDestroyObject(FIRE_P); + } + + SHPClose(FIRE_H); + + if (do_sort) + n_fires_month += fire_n; + + all_days[DAY] = std::move(fires); + } + + all_fires.push_back(month_info{ + .n_fires = n_fires_month, + .name = MONTH, + .days = std::move(all_days), + }); + } + + if (do_sort) { + std::sort(all_fires.begin(), all_fires.end(), + [](auto& a, auto& b) { return a.n_fires > b.n_fires; }); + } + + std::filesystem::create_directory(out_dir); + + std::vector workers; + std::mutex it_lock; + auto it = all_fires.begin(); + auto wc = std::thread::hardware_concurrency(); + if (wc == 0) { + wc = 1; + } else if (wc > 1) { + wc--; + } + std::cout << "Starting " << wc << " worker threads..." << std::endl; + for (unsigned int w = 1; w <= wc; w++) { + workers.push_back(std::thread([&it, &it_lock, &all_fires, &residentials, + &out_dir, w, distance]() { + for (;;) { + it_lock.lock(); + if (it == all_fires.end()) { + std::cout << "Worker " << w << " done!" << std::endl; + it_lock.unlock(); + return; + } + + const auto& month_data = *(it++); + auto& month = month_data.name; + auto& month_fires = month_data.days; + std::cout << "Worker " << w << " processing data from " << month << "..." << std::endl; + it_lock.unlock(); + + std::ofstream outf(out_dir / (month + ".csv")); + outf << "id"; + for (auto& month_data : month_fires) { + outf << ',' << month_data.first; + } + outf << '\n'; + + for (unsigned int i = 0; i < residentials.size(); i++) { + outf << i; + const auto& [resx, resy] = residentials[i]; + for (const auto& [day, fires] : month_fires) { + auto matchp = 0; + for (const auto& [firex, firey] : fires) { + const auto dx = resx - firex; + const auto dy = resy - firey; + if (dx * dx + dy * dy < distance * distance) { + matchp = 1; + break; + } + } + outf << ',' << matchp; + } + outf << '\n'; + } + } + })); + } + + for (auto& worker : workers) { + worker.join(); + } + + std::cout << "All done!" << std::endl; +}