Initial commit

This commit is contained in:
eriedaberrie 2024-01-08 17:14:18 -08:00
commit e2b3e1ebc1
7 changed files with 347 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

16
.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
.cache/
.direnv/
build*/
result*
/out/
/fires/
*.csv
*.sb[nx]
*.sh[px]
*.shp.xml
*.cpg
*.dbf
*.prj

20
default.nix Normal file
View file

@ -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";
}

26
flake.lock Normal file
View file

@ -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
}

42
flake.nix Normal file
View file

@ -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);
};
}

17
meson.build Normal file
View file

@ -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
)

225
src/main.cpp Normal file
View file

@ -0,0 +1,225 @@
#include <filesystem>
#include <fstream>
#include <GeographicLib/UTMUPS.hpp>
#include <iostream>
#include <map>
#include <mutex>
#include <shapefil.h>
#include <thread>
#include <unistd.h>
#include <vector>
struct month_info {
unsigned long long n_fires;
std::string name;
std::map<std::string, std::vector<std::pair<double, double>>> 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<char *>(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<std::pair<double, double>> 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<month_info> 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<std::string, std::vector<std::pair<double, double>>> 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<std::pair<double, double>> 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<std::thread> 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;
}