Initial commit
This commit is contained in:
commit
e2b3e1ebc1
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal 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
20
default.nix
Normal 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
26
flake.lock
Normal 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
42
flake.nix
Normal 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
17
meson.build
Normal 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
225
src/main.cpp
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue