From 47e0c8acaf11a737bce1625c7ecbcc960c6ccc8f Mon Sep 17 00:00:00 2001 From: Corey White Date: Sat, 15 Jun 2024 04:44:10 -0400 Subject: [PATCH 01/18] v.ppa: Point Pattern Analysis --- vector/v.ppa/Makefile | 14 ++ vector/v.ppa/local_proto.h | 0 vector/v.ppa/main.c | 360 +++++++++++++++++++++++++++ vector/v.ppa/testsuite/f_crashes.csv | 102 ++++++++ vector/v.ppa/testsuite/g_crashes.csv | 102 ++++++++ vector/v.ppa/testsuite/k_crashes.csv | 102 ++++++++ vector/v.ppa/testsuite/l_crashes.csv | 102 ++++++++ vector/v.ppa/testsuite/test_v_ppa.py | 0 vector/v.ppa/v.ppa.html | 24 ++ 9 files changed, 806 insertions(+) create mode 100644 vector/v.ppa/Makefile create mode 100644 vector/v.ppa/local_proto.h create mode 100644 vector/v.ppa/main.c create mode 100644 vector/v.ppa/testsuite/f_crashes.csv create mode 100644 vector/v.ppa/testsuite/g_crashes.csv create mode 100644 vector/v.ppa/testsuite/k_crashes.csv create mode 100644 vector/v.ppa/testsuite/l_crashes.csv create mode 100644 vector/v.ppa/testsuite/test_v_ppa.py create mode 100644 vector/v.ppa/v.ppa.html diff --git a/vector/v.ppa/Makefile b/vector/v.ppa/Makefile new file mode 100644 index 00000000000..a4cf9b1faee --- /dev/null +++ b/vector/v.ppa/Makefile @@ -0,0 +1,14 @@ + +MODULE_TOPDIR = ../.. + +PGM=v.ppa + +LIBES = $(VECTORLIB) $(DBMILIB) $(BTREE2LIB) $(GISLIB) +EXTRA_LIBS = $(OPENMP_LIBPATH) $(OPENMP_LIB) +DEPENDENCIES = $(VECTORDEP) $(DBMIDEP) $(BTREE2DEP) $(GISDEP) +EXTRA_INC = $(VECT_INC) $(OPENMP_INCPATH) +EXTRA_CFLAGS = $(VECT_CFLAGS) $(OPENMP_CFLAGS) + +include $(MODULE_TOPDIR)/include/Make/Module.make + +default: cmd diff --git a/vector/v.ppa/local_proto.h b/vector/v.ppa/local_proto.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c new file mode 100644 index 00000000000..8ae4c311e51 --- /dev/null +++ b/vector/v.ppa/main.c @@ -0,0 +1,360 @@ +#if defined(_OPENMP) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct Point { + double x, y; + int id; +}; + +void calculate_k_function(struct kdtree *kdtree, struct Point *points, int n, + const char *output_file); +void calculate_l_function(struct Point *points, int n, const char *output_file); +void calculate_f_function(struct Point *points, int n, const char *output_file); +void calculate_g_functoin(struct kdtree *kdtree, struct Point *points, int n, + const char *output_file); +double euclidean_distance(const struct Point *p1, const struct Point *p2); + +int main(int argc, char *argv[]) +{ + struct GModule *module; + struct Option *input_opt, *output_opt, *method_opt; + struct Map_info Map; + struct line_pnts *points; + struct line_cats *cats; + int i, nlines, line, n; + const char *input_vector, *output_file, *method; + + // Initialize the GIS environment + G_gisinit(argv[0]); + + // Set up module description + module = G_define_module(); + G_add_keyword(_("vector")); + G_add_keyword(_("point pattern analysis")); + module->description = + _("Point pattern analysis using K, L, F, and G functions."); + + // Define options + input_opt = G_define_standard_option(G_OPT_V_INPUT); + output_opt = G_define_standard_option(G_OPT_F_OUTPUT); + method_opt = G_define_option(); + method_opt->key = "method"; + method_opt->type = TYPE_STRING; + method_opt->required = YES; + method_opt->options = "k,l,f,g"; + method_opt->description = _("Method to calculate (k, l, f, g)"); + + if (G_parser(argc, argv)) + exit(EXIT_FAILURE); + + input_vector = input_opt->answer; + output_file = output_opt->answer; + method = method_opt->answer; + + // Open the vector map + if (Vect_open_old(&Map, input_vector, "") < 0) + G_fatal_error(_("Unable to open vector map <%s>"), input_vector); + + // Allocate memory for points + points = Vect_new_line_struct(); + cats = Vect_new_cats_struct(); + nlines = Vect_get_num_lines(&Map); + n = 0; + + // Count points + for (line = 1; line <= nlines; line++) { + if (Vect_read_line(&Map, points, cats, line) == GV_POINT) { + n++; + } + } + + // Allocate memory for points array + struct Point *pts = (struct Point *)malloc(n * sizeof(struct Point)); + int idx = 0; + + // Store points + // Initialize k-d tree + struct kdtree *kdtree = kdtree_create(2, NULL); + for (line = 1; line <= nlines; line++) { + if (Vect_read_line(&Map, points, cats, line) == GV_POINT) { + pts[idx].x = points->x[0]; + pts[idx].y = points->y[0]; + pts[idx].id = idx; + double coords[2] = {points->x[0], points->y[0]}; + kdtree_insert(kdtree, coords, idx, 0); + idx++; + } + } + + G_message(_("Number of points: %d"), n); + G_message(_("Method: %s"), method); + + if (strcmp(method, "k") == 0) { + calculate_k_function(kdtree, pts, n, output_file); + } + else if (strcmp(method, "l") == 0) { + calculate_l_function(pts, n, output_file); + } + else if (strcmp(method, "f") == 0) { + calculate_f_function(pts, n, output_file); + } + else if (strcmp(method, "g") == 0) { + calculate_g_functoin(kdtree, pts, n, output_file); + } + else { + G_fatal_error(_("Method not implemented yet")); + } + + // Free memory and close the vector map + free(pts); + Vect_close(&Map); + + return 0; +} + +/** + * Calculates the k-function for a given set of points using a k-d tree. + * + * @param kdtree The k-d tree used for nearest neighbor searches. + * @param points An array of Point structures representing the points. + * @param n The number of points in the array. + * @param output_file The path to the output file where the results will be + * saved. + */ +void calculate_k_function(struct kdtree *kdtree, struct Point *points, int n, + const char *output_file) +{ + FILE *fp = fopen(output_file, "w"); + if (fp == NULL) { + G_fatal_error(_("Unable to open output file <%s>"), output_file); + } + + double max_dist = 0.0; +#pragma omp parallel for reduction(max : max_dist) + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j) { + double dist = euclidean_distance(&points[i], &points[j]); + if (dist > max_dist) { + max_dist = dist; + } + } + } + } + + G_message(_("Max distance: %f"), max_dist); + + fprintf(fp, "Distance,K-value\n"); + for (double d = 0; d <= max_dist; d += max_dist / 100) { + double k_value = 0.0; + +#pragma omp parallel for reduction(+ : k_value) + for (int i = 0; i < n; i++) { + double coords[2] = {points[i].x, points[i].y}; + int *puid = NULL; + double *pd = NULL; + int count = + kdtree_dnn(kdtree, coords, &puid, &pd, d, &points[i].id); + k_value += count - 1; // subtract 1 to exclude the point itself + free(puid); + free(pd); + } + + k_value /= (n * (n - 1)); + fprintf(fp, "%f,%f\n", d, k_value); + } + + kdtree_destroy(kdtree); + fclose(fp); +} + +double euclidean_distance(const struct Point *p1, const struct Point *p2) +{ + return sqrt((p1->x - p2->x) * (p1->x - p2->x) + + (p1->y - p2->y) * (p1->y - p2->y)); +} + +/** + * Calculates the L function for a given array of points. + * L(d) = √(K(d) / π) + * + * @param points The array of points. + * @param n The number of points in the array. + * @param output_file The path to the output file where the results will be + * written. + */ +void calculate_l_function(struct Point *points, int n, const char *output_file) +{ + + FILE *fp = fopen(output_file, "w"); + if (fp == NULL) { + G_fatal_error(_("Unable to open output file <%s>"), output_file); + } + + double **distances = (double **)malloc(n * sizeof(double *)); + for (int i = 0; i < n; i++) { + distances[i] = (double *)malloc(n * sizeof(double)); + } + + double max_dist = 0.0; +#pragma omp parallel for reduction(max : max_dist) + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j) { + double dist = euclidean_distance(&points[i], &points[j]); + distances[i][j] = dist; + if (dist > max_dist) { + max_dist = dist; + } + } + } + } + + G_message(_("Max distance: %f"), max_dist); + + fprintf(fp, "Distance,L-value\n"); + for (double d = 0; d <= max_dist; d += max_dist / 100) { + double k_value = 0.0; + +#pragma omp parallel for reduction(+ : k_value) + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j && distances[i][j] <= d) { + k_value += 1; + } + } + } + + k_value /= (n * (n - 1)); + double l_value = sqrt(k_value / M_PI); + fprintf(fp, "%f,%f\n", d, l_value); + } + + for (int i = 0; i < n; i++) { + free(distances[i]); + } + free(distances); + fclose(fp); +} + +/** + * Calculates the f function for a given array of points. + * F(d) = (1/n) * Σ I(d_ij ≤ d) + * + * @param points The array of points. + * @param n The number of points in the array. + * @param output_file The output file to write the results to. + */ +void calculate_f_function(struct Point *points, int n, const char *output_file) +{ + FILE *fp = fopen(output_file, "w"); + if (fp == NULL) { + G_fatal_error(_("Unable to open output file <%s>"), output_file); + } + + double *distances = (double *)malloc(n * sizeof(double)); + double max_dist = 0.0; +#pragma omp parallel for reduction(max : max_dist) + for (int i = 0; i < n; i++) { + double min_dist = DBL_MAX; + for (int j = 0; j < n; j++) { + if (i != j) { + double dist = euclidean_distance(&points[i], &points[j]); + if (dist < min_dist) { + min_dist = dist; + } + if (dist > max_dist) { + max_dist = dist; + } + } + } + distances[i] = min_dist; + } + + G_message(_("Max distance: %f"), max_dist); + + fprintf(fp, "Distance,F-value\n"); + for (double d = 0; d <= max_dist; d += max_dist / 100) { + double f_value = 0.0; + +#pragma omp parallel for reduction(+ : f_value) + for (int i = 0; i < n; i++) { + if (distances[i] <= d) { + f_value += 1; + } + } + + f_value /= n; + fprintf(fp, "%f,%f\n", d, f_value); + } + + free(distances); + fclose(fp); +} + +/** + * Calculates the g function using a k-d tree and an array of points. + * + * @param kdtree The k-d tree used for nearest neighbor search. + * @param points An array of points. + * @param n The number of points in the array. + * @param output_file The path to the output file where the results will be + * written. + */ +void calculate_g_functoin(struct kdtree *kdtree, struct Point *points, int n, + const char *output_file) +{ + G_message(_("G-Funtion")); + FILE *fp = fopen(output_file, "w"); + if (fp == NULL) { + G_fatal_error(_("Unable to open output file <%s>"), output_file); + } + + double max_dist = 0.0; +#pragma omp parallel for reduction(max : max_dist) + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j) { + double dist = euclidean_distance(&points[i], &points[j]); + if (dist > max_dist) { + max_dist = dist; + } + } + } + } + + fprintf(fp, "Distance,G-value\n"); + for (double d = 0; d <= max_dist; d += max_dist / 100) { + G_percent(d, max_dist, 4); + double g_value = 0.0; + +#pragma omp parallel for reduction(+ : g_value) + for (int i = 0; i < n; i++) { + double coords[2] = {points[i].x, points[i].y}; + int puid = 0; + double pd = 0.0; + kdtree_knn(kdtree, coords, &puid, &pd, 1, &points[i].id); + + if (pd <= d) { + g_value += 1; // add the distance to the nearest neighbor + } + } + + // Normalize g-value + g_value /= n; + fprintf(fp, "%f,%f\n", d, g_value); + } + + fclose(fp); +} diff --git a/vector/v.ppa/testsuite/f_crashes.csv b/vector/v.ppa/testsuite/f_crashes.csv new file mode 100644 index 00000000000..67ab442d80e --- /dev/null +++ b/vector/v.ppa/testsuite/f_crashes.csv @@ -0,0 +1,102 @@ +Distance,F-value +0.000000,0.038647 +267.884649,0.454106 +535.769298,0.729469 +803.653948,0.874396 +1071.538597,0.908213 +1339.423246,0.946860 +1607.307895,0.966184 +1875.192544,0.971014 +2143.077193,0.971014 +2410.961843,0.971014 +2678.846492,0.975845 +2946.731141,0.975845 +3214.615790,0.985507 +3482.500439,0.995169 +3750.385089,0.995169 +4018.269738,0.995169 +4286.154387,1.000000 +4554.039036,1.000000 +4821.923685,1.000000 +5089.808334,1.000000 +5357.692984,1.000000 +5625.577633,1.000000 +5893.462282,1.000000 +6161.346931,1.000000 +6429.231580,1.000000 +6697.116230,1.000000 +6965.000879,1.000000 +7232.885528,1.000000 +7500.770177,1.000000 +7768.654826,1.000000 +8036.539475,1.000000 +8304.424125,1.000000 +8572.308774,1.000000 +8840.193423,1.000000 +9108.078072,1.000000 +9375.962721,1.000000 +9643.847371,1.000000 +9911.732020,1.000000 +10179.616669,1.000000 +10447.501318,1.000000 +10715.385967,1.000000 +10983.270616,1.000000 +11251.155266,1.000000 +11519.039915,1.000000 +11786.924564,1.000000 +12054.809213,1.000000 +12322.693862,1.000000 +12590.578512,1.000000 +12858.463161,1.000000 +13126.347810,1.000000 +13394.232459,1.000000 +13662.117108,1.000000 +13930.001757,1.000000 +14197.886407,1.000000 +14465.771056,1.000000 +14733.655705,1.000000 +15001.540354,1.000000 +15269.425003,1.000000 +15537.309653,1.000000 +15805.194302,1.000000 +16073.078951,1.000000 +16340.963600,1.000000 +16608.848249,1.000000 +16876.732898,1.000000 +17144.617548,1.000000 +17412.502197,1.000000 +17680.386846,1.000000 +17948.271495,1.000000 +18216.156144,1.000000 +18484.040794,1.000000 +18751.925443,1.000000 +19019.810092,1.000000 +19287.694741,1.000000 +19555.579390,1.000000 +19823.464039,1.000000 +20091.348689,1.000000 +20359.233338,1.000000 +20627.117987,1.000000 +20895.002636,1.000000 +21162.887285,1.000000 +21430.771935,1.000000 +21698.656584,1.000000 +21966.541233,1.000000 +22234.425882,1.000000 +22502.310531,1.000000 +22770.195180,1.000000 +23038.079830,1.000000 +23305.964479,1.000000 +23573.849128,1.000000 +23841.733777,1.000000 +24109.618426,1.000000 +24377.503076,1.000000 +24645.387725,1.000000 +24913.272374,1.000000 +25181.157023,1.000000 +25449.041672,1.000000 +25716.926321,1.000000 +25984.810971,1.000000 +26252.695620,1.000000 +26520.580269,1.000000 +26788.464918,1.000000 diff --git a/vector/v.ppa/testsuite/g_crashes.csv b/vector/v.ppa/testsuite/g_crashes.csv new file mode 100644 index 00000000000..1e58532202a --- /dev/null +++ b/vector/v.ppa/testsuite/g_crashes.csv @@ -0,0 +1,102 @@ +Distance,G-value +0.000000,0.019324 +267.884649,0.048309 +535.769298,0.067633 +803.653948,0.096618 +1071.538597,0.106280 +1339.423246,0.106280 +1607.307895,0.106280 +1875.192544,0.106280 +2143.077193,0.125604 +2410.961843,0.125604 +2678.846492,0.125604 +2946.731141,0.125604 +3214.615790,0.125604 +3482.500439,0.125604 +3750.385089,0.125604 +4018.269738,0.144928 +4286.154387,0.149758 +4554.039036,0.149758 +4821.923685,0.149758 +5089.808334,0.149758 +5357.692984,0.149758 +5625.577633,0.149758 +5893.462282,0.149758 +6161.346931,0.149758 +6429.231580,0.159420 +6697.116230,0.159420 +6965.000879,0.159420 +7232.885528,0.169082 +7500.770177,0.169082 +7768.654826,0.178744 +8036.539475,0.178744 +8304.424125,0.183575 +8572.308774,0.193237 +8840.193423,0.193237 +9108.078072,0.193237 +9375.962721,0.207729 +9643.847371,0.207729 +9911.732020,0.207729 +10179.616669,0.217391 +10447.501318,0.217391 +10715.385967,0.217391 +10983.270616,0.227053 +11251.155266,0.227053 +11519.039915,0.227053 +11786.924564,0.236715 +12054.809213,0.241546 +12322.693862,0.241546 +12590.578512,0.241546 +12858.463161,0.246377 +13126.347810,0.256039 +13394.232459,0.256039 +13662.117108,0.256039 +13930.001757,0.256039 +14197.886407,0.260870 +14465.771056,0.260870 +14733.655705,0.265700 +15001.540354,0.265700 +15269.425003,0.289855 +15537.309653,0.289855 +15805.194302,0.289855 +16073.078951,0.289855 +16340.963600,0.289855 +16608.848249,0.289855 +16876.732898,0.289855 +17144.617548,0.289855 +17412.502197,0.289855 +17680.386846,0.289855 +17948.271495,0.294686 +18216.156144,0.294686 +18484.040794,0.294686 +18751.925443,0.304348 +19019.810092,0.304348 +19287.694741,0.304348 +19555.579390,0.304348 +19823.464039,0.304348 +20091.348689,0.304348 +20359.233338,0.304348 +20627.117987,0.309179 +20895.002636,0.309179 +21162.887285,0.309179 +21430.771935,0.309179 +21698.656584,0.309179 +21966.541233,0.309179 +22234.425882,0.309179 +22502.310531,0.309179 +22770.195180,0.309179 +23038.079830,0.309179 +23305.964479,0.309179 +23573.849128,0.309179 +23841.733777,0.309179 +24109.618426,0.309179 +24377.503076,0.309179 +24645.387725,0.309179 +24913.272374,0.309179 +25181.157023,0.314010 +25449.041672,0.314010 +25716.926321,0.314010 +25984.810971,0.314010 +26252.695620,0.323671 +26520.580269,0.323671 +26788.464918,0.323671 diff --git a/vector/v.ppa/testsuite/k_crashes.csv b/vector/v.ppa/testsuite/k_crashes.csv new file mode 100644 index 00000000000..3f4da08f3e6 --- /dev/null +++ b/vector/v.ppa/testsuite/k_crashes.csv @@ -0,0 +1,102 @@ +Distance,K-value +0.000000,-0.004761 +267.884649,-0.000797 +535.769298,0.009310 +803.653948,0.021528 +1071.538597,0.038296 +1339.423246,0.056001 +1607.307895,0.076497 +1875.192544,0.101168 +2143.077193,0.128676 +2410.961843,0.158670 +2678.846492,0.188734 +2946.731141,0.216547 +3214.615790,0.245509 +3482.500439,0.272220 +3750.385089,0.302214 +4018.269738,0.328878 +4286.154387,0.354861 +4554.039036,0.380447 +4821.923685,0.404062 +5089.808334,0.427419 +5357.692984,0.449791 +5625.577633,0.475799 +5893.462282,0.498687 +6161.346931,0.523709 +6429.231580,0.549951 +6697.116230,0.571784 +6965.000879,0.594344 +7232.885528,0.615590 +7500.770177,0.637658 +7768.654826,0.659326 +8036.539475,0.679823 +8304.424125,0.697880 +8572.308774,0.714390 +8840.193423,0.728906 +9108.078072,0.744735 +9375.962721,0.761667 +9643.847371,0.778857 +9911.732020,0.792716 +10179.616669,0.807959 +10447.501318,0.820951 +10715.385967,0.833568 +10983.270616,0.844918 +11251.155266,0.855354 +11519.039915,0.865719 +11786.924564,0.876038 +12054.809213,0.883894 +12322.693862,0.891726 +12590.578512,0.898293 +12858.463161,0.905047 +13126.347810,0.911050 +13394.232459,0.916397 +13662.117108,0.921978 +13930.001757,0.926622 +14197.886407,0.930796 +14465.771056,0.934337 +14733.655705,0.937784 +15001.540354,0.941513 +15269.425003,0.944843 +15537.309653,0.947798 +15805.194302,0.950847 +16073.078951,0.953051 +16340.963600,0.954810 +16608.848249,0.956639 +16876.732898,0.958515 +17144.617548,0.960227 +17412.502197,0.961962 +17680.386846,0.962854 +17948.271495,0.963510 +18216.156144,0.964612 +18484.040794,0.965691 +18751.925443,0.966137 +19019.810092,0.966887 +19287.694741,0.967825 +19555.579390,0.968388 +19823.464039,0.969396 +20091.348689,0.969772 +20359.233338,0.970663 +20627.117987,0.971179 +20895.002636,0.972046 +21162.887285,0.972726 +21430.771935,0.973195 +21698.656584,0.973711 +21966.541233,0.973899 +22234.425882,0.974087 +22502.310531,0.974415 +22770.195180,0.974415 +23038.079830,0.974603 +23305.964479,0.974696 +23573.849128,0.974767 +23841.733777,0.974907 +24109.618426,0.974907 +24377.503076,0.975165 +24645.387725,0.975306 +24913.272374,0.975400 +25181.157023,0.975494 +25449.041672,0.975587 +25716.926321,0.975634 +25984.810971,0.975634 +26252.695620,0.975728 +26520.580269,0.975728 +26788.464918,0.975775 diff --git a/vector/v.ppa/testsuite/l_crashes.csv b/vector/v.ppa/testsuite/l_crashes.csv new file mode 100644 index 00000000000..cb69f785242 --- /dev/null +++ b/vector/v.ppa/testsuite/l_crashes.csv @@ -0,0 +1,102 @@ +Distance,L-value +0.000000,0.007728 +267.884649,0.036656 +535.769298,0.068030 +803.653948,0.092733 +1071.538597,0.118464 +1339.423246,0.140487 +1607.307895,0.162282 +1875.192544,0.185143 +2143.077193,0.207680 +2410.961843,0.229891 +2678.846492,0.250138 +2946.731141,0.267389 +3214.615790,0.284407 +3482.500439,0.299144 +3750.385089,0.314851 +4018.269738,0.328223 +4286.154387,0.340678 +4554.039036,0.352586 +4821.923685,0.363306 +5089.808334,0.373537 +5357.692984,0.383107 +5625.577633,0.394000 +5893.462282,0.403251 +6161.346931,0.413198 +6429.231580,0.423388 +6697.116230,0.431716 +6965.000879,0.440107 +7232.885528,0.447858 +7500.770177,0.455886 +7768.654826,0.463550 +8036.539475,0.470694 +8304.424125,0.476917 +8572.308774,0.482488 +8840.193423,0.487352 +9108.078072,0.492669 +9375.962721,0.498273 +9643.847371,0.503845 +9911.732020,0.508373 +10179.616669,0.513239 +10447.501318,0.517353 +10715.385967,0.521349 +10983.270616,0.524874 +11251.155266,0.528078 +11519.039915,0.531319 +11786.924564,0.534471 +12054.809213,0.536854 +12322.693862,0.539226 +12590.578512,0.541216 +12858.463161,0.543295 +13126.347810,0.545092 +13394.232459,0.546678 +13662.117108,0.548328 +13930.001757,0.549715 +14197.886407,0.550949 +14465.771056,0.551978 +14733.655705,0.553018 +15001.540354,0.554110 +15269.425003,0.555106 +15537.309653,0.555980 +15805.194302,0.556865 +16073.078951,0.557535 +16340.963600,0.558043 +16608.848249,0.558592 +16876.732898,0.559153 +17144.617548,0.559660 +17412.502197,0.560180 +17680.386846,0.560433 +17948.271495,0.560619 +18216.156144,0.560939 +18484.040794,0.561258 +18751.925443,0.561391 +19019.810092,0.561604 +19287.694741,0.561869 +19555.579390,0.562029 +19823.464039,0.562334 +20091.348689,0.562440 +20359.233338,0.562706 +20627.117987,0.562852 +20895.002636,0.563104 +21162.887285,0.563302 +21430.771935,0.563435 +21698.656584,0.563581 +21966.541233,0.563634 +22234.425882,0.563687 +22502.310531,0.563779 +22770.195180,0.563779 +23038.079830,0.563832 +23305.964479,0.563859 +23573.849128,0.563885 +23841.733777,0.563925 +24109.618426,0.563925 +24377.503076,0.564004 +24645.387725,0.564044 +24913.272374,0.564070 +25181.157023,0.564097 +25449.041672,0.564123 +25716.926321,0.564137 +25984.810971,0.564137 +26252.695620,0.564163 +26520.580269,0.564163 +26788.464918,0.564176 diff --git a/vector/v.ppa/testsuite/test_v_ppa.py b/vector/v.ppa/testsuite/test_v_ppa.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/vector/v.ppa/v.ppa.html b/vector/v.ppa/v.ppa.html new file mode 100644 index 00000000000..06a97ddc164 --- /dev/null +++ b/vector/v.ppa/v.ppa.html @@ -0,0 +1,24 @@ +

DESCRIPTION

+ +v.ppa allows users to perform point pattern analysis with the F, G, L, K, and bivariate Ripley's K functions. + +

Notes

+ +

EXAMPLES

+ +Calcuate the F-value for crash data to determine the distribution of distances from random points in the computational region ot the +nearest crash location. +
+   v.ppa input=crime output=crime_f.csv method=f
+
+ +
+Calculate the G-Value +
+   v.ppa input=crime output=crime_g.csv method=g
+
+ + +

AUTHORS

+Corey T. White, OpenPlains Inc. +
From f073c8c6f29f4798a26490e35dd64f2b24f91691 Mon Sep 17 00:00:00 2001 From: Corey White Date: Sun, 16 Jun 2024 04:10:00 -0400 Subject: [PATCH 02/18] Changed how vector ponits are counted --- vector/v.ppa/main.c | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index 8ae4c311e51..7c8d5343a31 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -27,28 +27,22 @@ double euclidean_distance(const struct Point *p1, const struct Point *p2); int main(int argc, char *argv[]) { - struct GModule *module; - struct Option *input_opt, *output_opt, *method_opt; struct Map_info Map; - struct line_pnts *points; - struct line_cats *cats; - int i, nlines, line, n; - const char *input_vector, *output_file, *method; // Initialize the GIS environment G_gisinit(argv[0]); // Set up module description - module = G_define_module(); + struct GModule *module = G_define_module(); G_add_keyword(_("vector")); G_add_keyword(_("point pattern analysis")); module->description = _("Point pattern analysis using K, L, F, and G functions."); // Define options - input_opt = G_define_standard_option(G_OPT_V_INPUT); - output_opt = G_define_standard_option(G_OPT_F_OUTPUT); - method_opt = G_define_option(); + struct Option *input_opt = G_define_standard_option(G_OPT_V_INPUT); + struct Option *output_opt = G_define_standard_option(G_OPT_F_OUTPUT); + struct Option *method_opt = G_define_option(); method_opt->key = "method"; method_opt->type = TYPE_STRING; method_opt->required = YES; @@ -58,26 +52,20 @@ int main(int argc, char *argv[]) if (G_parser(argc, argv)) exit(EXIT_FAILURE); - input_vector = input_opt->answer; - output_file = output_opt->answer; - method = method_opt->answer; + const char *input_vector = input_opt->answer; + const char *output_file = output_opt->answer; + const char *method = method_opt->answer; // Open the vector map if (Vect_open_old(&Map, input_vector, "") < 0) G_fatal_error(_("Unable to open vector map <%s>"), input_vector); // Allocate memory for points - points = Vect_new_line_struct(); - cats = Vect_new_cats_struct(); - nlines = Vect_get_num_lines(&Map); - n = 0; + struct line_pnts *points = Vect_new_line_struct(); + struct line_cats *cats = Vect_new_cats_struct(); + int nlines = Vect_get_num_lines(&Map); - // Count points - for (line = 1; line <= nlines; line++) { - if (Vect_read_line(&Map, points, cats, line) == GV_POINT) { - n++; - } - } + int n = Vect_get_num_primitives(&Map, GV_POINT); // Allocate memory for points array struct Point *pts = (struct Point *)malloc(n * sizeof(struct Point)); @@ -86,7 +74,8 @@ int main(int argc, char *argv[]) // Store points // Initialize k-d tree struct kdtree *kdtree = kdtree_create(2, NULL); - for (line = 1; line <= nlines; line++) { + + for (int line = 1; line <= nlines; line++) { if (Vect_read_line(&Map, points, cats, line) == GV_POINT) { pts[idx].x = points->x[0]; pts[idx].y = points->y[0]; From 4c2a929f162bb9b5450f8170ab1c45a72370f467 Mon Sep 17 00:00:00 2001 From: Corey White Date: Tue, 18 Jun 2024 15:17:16 -0400 Subject: [PATCH 03/18] Update vector/v.ppa/main.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Loïc Bartoletti --- vector/v.ppa/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index 7c8d5343a31..314d8ea53ec 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -301,7 +301,7 @@ void calculate_f_function(struct Point *points, int n, const char *output_file) * @param output_file The path to the output file where the results will be * written. */ -void calculate_g_functoin(struct kdtree *kdtree, struct Point *points, int n, +void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, const char *output_file) { G_message(_("G-Funtion")); From 4506cfedd54e0288c87d0c8f9dbf054472eef216 Mon Sep 17 00:00:00 2001 From: Corey White Date: Tue, 18 Jun 2024 15:17:40 -0400 Subject: [PATCH 04/18] Update vector/v.ppa/main.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Loïc Bartoletti --- vector/v.ppa/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index 314d8ea53ec..bfdc0834f86 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -304,7 +304,7 @@ void calculate_f_function(struct Point *points, int n, const char *output_file) void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, const char *output_file) { - G_message(_("G-Funtion")); + G_message(_("G-Function")); FILE *fp = fopen(output_file, "w"); if (fp == NULL) { G_fatal_error(_("Unable to open output file <%s>"), output_file); From 3c7deb0c9ee830ed671b58708eb9c03420290961 Mon Sep 17 00:00:00 2001 From: Corey White Date: Tue, 18 Jun 2024 15:18:43 -0400 Subject: [PATCH 05/18] Update vector/v.ppa/main.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Loïc Bartoletti --- vector/v.ppa/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index bfdc0834f86..3936e63416b 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -336,7 +336,7 @@ void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, kdtree_knn(kdtree, coords, &puid, &pd, 1, &points[i].id); if (pd <= d) { - g_value += 1; // add the distance to the nearest neighbor + g_value += 1.0; // add the distance to the nearest neighbor } } From cfe7f32aa04206884e441293efa605bba39f3d4c Mon Sep 17 00:00:00 2001 From: Corey White Date: Tue, 18 Jun 2024 15:18:53 -0400 Subject: [PATCH 06/18] Update vector/v.ppa/main.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Loïc Bartoletti --- vector/v.ppa/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index 3936e63416b..6f722acb91c 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -274,7 +274,7 @@ void calculate_f_function(struct Point *points, int n, const char *output_file) G_message(_("Max distance: %f"), max_dist); fprintf(fp, "Distance,F-value\n"); - for (double d = 0; d <= max_dist; d += max_dist / 100) { + for (double d = 0.0; d <= max_dist; d += max_dist / 100.0) { double f_value = 0.0; #pragma omp parallel for reduction(+ : f_value) From 96cccdf94f50850c59dc1bb5c9a7b16b33fc66dc Mon Sep 17 00:00:00 2001 From: Corey White Date: Tue, 18 Jun 2024 15:21:41 -0400 Subject: [PATCH 07/18] Update vector/v.ppa/main.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Loïc Bartoletti --- vector/v.ppa/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index 6f722acb91c..b1453f28fbb 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -220,7 +220,7 @@ void calculate_l_function(struct Point *points, int n, const char *output_file) for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (i != j && distances[i][j] <= d) { - k_value += 1; + k_value += 1.0; } } } From 81de2e50c36e01944143dcc20800bf20ef670e36 Mon Sep 17 00:00:00 2001 From: Corey White Date: Tue, 18 Jun 2024 15:22:33 -0400 Subject: [PATCH 08/18] Update vector/v.ppa/main.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Loïc Bartoletti --- vector/v.ppa/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index b1453f28fbb..d27d82f8b02 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -213,7 +213,7 @@ void calculate_l_function(struct Point *points, int n, const char *output_file) G_message(_("Max distance: %f"), max_dist); fprintf(fp, "Distance,L-value\n"); - for (double d = 0; d <= max_dist; d += max_dist / 100) { + for (double d = 0.0; d <= max_dist; d += max_dist / 100.0) { double k_value = 0.0; #pragma omp parallel for reduction(+ : k_value) From 2826c82efc3600d366f9bb89e306b43e03e8b188 Mon Sep 17 00:00:00 2001 From: Corey White Date: Tue, 18 Jun 2024 15:38:44 -0400 Subject: [PATCH 09/18] Moved sqrt call outside of ec. distance loop --- vector/v.ppa/main.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index d27d82f8b02..8f4783c911e 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -21,7 +21,7 @@ void calculate_k_function(struct kdtree *kdtree, struct Point *points, int n, const char *output_file); void calculate_l_function(struct Point *points, int n, const char *output_file); void calculate_f_function(struct Point *points, int n, const char *output_file); -void calculate_g_functoin(struct kdtree *kdtree, struct Point *points, int n, +void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, const char *output_file); double euclidean_distance(const struct Point *p1, const struct Point *p2); @@ -99,7 +99,7 @@ int main(int argc, char *argv[]) calculate_f_function(pts, n, output_file); } else if (strcmp(method, "g") == 0) { - calculate_g_functoin(kdtree, pts, n, output_file); + calculate_g_function(kdtree, pts, n, output_file); } else { G_fatal_error(_("Method not implemented yet")); @@ -170,8 +170,8 @@ void calculate_k_function(struct kdtree *kdtree, struct Point *points, int n, double euclidean_distance(const struct Point *p1, const struct Point *p2) { - return sqrt((p1->x - p2->x) * (p1->x - p2->x) + - (p1->y - p2->y) * (p1->y - p2->y)); + return (p1->x - p2->x) * (p1->x - p2->x) + + (p1->y - p2->y) * (p1->y - p2->y); } /** @@ -209,7 +209,7 @@ void calculate_l_function(struct Point *points, int n, const char *output_file) } } } - + max_dist = sqrt(max_dist); G_message(_("Max distance: %f"), max_dist); fprintf(fp, "Distance,L-value\n"); @@ -268,9 +268,9 @@ void calculate_f_function(struct Point *points, int n, const char *output_file) } } } - distances[i] = min_dist; + distances[i] = sqrt(min_dist); } - + max_dist = sqrt(max_dist); G_message(_("Max distance: %f"), max_dist); fprintf(fp, "Distance,F-value\n"); @@ -322,7 +322,7 @@ void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, } } } - + max_dist = sqrt(max_dist); fprintf(fp, "Distance,G-value\n"); for (double d = 0; d <= max_dist; d += max_dist / 100) { G_percent(d, max_dist, 4); From 22036f43ba0100e5aa42f395703240d48afa2048 Mon Sep 17 00:00:00 2001 From: Corey White Date: Tue, 25 Jun 2024 15:24:31 -0400 Subject: [PATCH 10/18] Updated docs --- vector/v.ppa/v.ppa.html | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/vector/v.ppa/v.ppa.html b/vector/v.ppa/v.ppa.html index 06a97ddc164..f4d2c36004e 100644 --- a/vector/v.ppa/v.ppa.html +++ b/vector/v.ppa/v.ppa.html @@ -1,21 +1,44 @@

DESCRIPTION

-v.ppa allows users to perform point pattern analysis with the F, G, L, K, and bivariate Ripley's K functions. +v.ppa allows users to perform point pattern analysis with the G, F, K, L, and bivariate Ripley's K functions.

Notes

EXAMPLES

-Calcuate the F-value for crash data to determine the distribution of distances from random points in the computational region ot the +

G Function

+Determine the distribution of distances from each point to its nearest neighbor to learn if the points are clustered, dispersed, or randomly distributed across the computational region. +
+   v.ppa input=crash output=crash_g.csv method=g
+
+ +
+

F Function

+Determine the distribution of the distances from random points in the computational region to the points in the input vector map. +This function is to determine if the points are clustered, dispersed, or randomly distributed across the computational region. +The output file will contain the distances from the random points to the nearest crash location.
-   v.ppa input=crime output=crime_f.csv method=f
+   v.ppa input=crash output=crash_f.csv method=f
 

-Calculate the G-Value +

K and L Functions

+Determine if the points are clustered, dispersed, or randomly distributed across spatial scales in the compuational region. +
+   v.ppa input=crash output=crash_k.csv method=k
+
+ +The L-Function is a transformation of the K-Function that allows for easier interpretation of the results. + +
    +
  • If L(r) is less than the expected value, the points are dispersed.
  • +
  • If L(r) is greater than the expected value, the points are clustered.
  • +
  • If L(r) is equal to the expected value, the points are randomly distributed (Poisson Process).
  • +
+
-   v.ppa input=crime output=crime_g.csv method=g
+   v.ppa input=crash output=crash_l.csv method=l
 
From dd6bef2c242ec638503c97cb6112ee34fa18c4a9 Mon Sep 17 00:00:00 2001 From: Corey White Date: Tue, 25 Jun 2024 17:11:42 -0400 Subject: [PATCH 11/18] added random seed for random points --- vector/v.ppa/main.c | 108 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 84 insertions(+), 24 deletions(-) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index 8f4783c911e..081c791a7e3 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -17,13 +17,18 @@ struct Point { int id; }; +void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, + const char *output_file); +void calculate_f_function(struct kdtree *kdtree, struct Point *points, int n, + const char *output_file, struct bound_box *box, + int num_random_points); void calculate_k_function(struct kdtree *kdtree, struct Point *points, int n, const char *output_file); void calculate_l_function(struct Point *points, int n, const char *output_file); -void calculate_f_function(struct Point *points, int n, const char *output_file); -void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, - const char *output_file); + double euclidean_distance(const struct Point *p1, const struct Point *p2); +void generate_random_points(struct Point *random_points, int num_random_points, + struct bound_box *box); int main(int argc, char *argv[]) { @@ -37,7 +42,7 @@ int main(int argc, char *argv[]) G_add_keyword(_("vector")); G_add_keyword(_("point pattern analysis")); module->description = - _("Point pattern analysis using K, L, F, and G functions."); + _("Point pattern analysis using G, F, K, and L functions."); // Define options struct Option *input_opt = G_define_standard_option(G_OPT_V_INPUT); @@ -47,14 +52,47 @@ int main(int argc, char *argv[]) method_opt->type = TYPE_STRING; method_opt->required = YES; method_opt->options = "k,l,f,g"; - method_opt->description = _("Method to calculate (k, l, f, g)"); + method_opt->description = _("Method to calculate (g, f, k, l)"); + + struct Option *random_points_opt = G_define_option(); + random_points_opt->key = "random_points"; + random_points_opt->type = TYPE_INTEGER; + random_points_opt->required = NO; + random_points_opt->answer = "1000"; + random_points_opt->description = + _("Number of random points for F-function calculation (default: 1000)"); + + struct Option *random_seed = G_define_option(); + random_seed->key = "seed"; + random_seed->type = TYPE_INTEGER; + random_seed->required = NO; + random_seed->label = _("Seed for random number generator"); + random_seed->description = + _("The same seed can be used to obtain same results" + " or random seed can be generated by other means."); if (G_parser(argc, argv)) exit(EXIT_FAILURE); + /****** INITIALISE RANDOM NUMBER GENERATOR ******/ + long seed_value; + if (random_seed->answer) { + seed_value = atol(random_seed->answer); + G_srand48(seed_value); + G_verbose_message(_("Read random seed from %s option: %ld"), + random_seed->key, seed_value); + } + else { + /* default as it used to be */ + seed_value = G_srand48_auto(); + G_verbose_message(_("Autogenerated random seed set to: %ld"), + seed_value); + } + const char *input_vector = input_opt->answer; const char *output_file = output_opt->answer; const char *method = method_opt->answer; + int num_random_points = atoi(random_points_opt->answer); // Open the vector map if (Vect_open_old(&Map, input_vector, "") < 0) @@ -96,7 +134,11 @@ int main(int argc, char *argv[]) calculate_l_function(pts, n, output_file); } else if (strcmp(method, "f") == 0) { - calculate_f_function(pts, n, output_file); + // Get bounding box + struct bound_box box; + Vect_get_map_box(&Map, &box); + calculate_f_function(kdtree, pts, n, output_file, &box, + num_random_points); } else if (strcmp(method, "g") == 0) { calculate_g_function(kdtree, pts, n, output_file); @@ -107,6 +149,7 @@ int main(int argc, char *argv[]) // Free memory and close the vector map free(pts); + kdtree_destroy(kdtree); // Ensure k-d tree is destroyed Vect_close(&Map); return 0; @@ -174,6 +217,16 @@ double euclidean_distance(const struct Point *p1, const struct Point *p2) (p1->y - p2->y) * (p1->y - p2->y); } +void generate_random_points(struct Point *random_points, int num_random_points, + struct bound_box *box) +{ + for (int i = 0; i < num_random_points; i++) { + random_points[i].x = box->W + (box->E - box->W) * (double)G_drand48(); + random_points[i].y = box->S + (box->N - box->S) * (double)G_drand48(); + random_points[i].id = i; + } +} + /** * Calculates the L function for a given array of points. * L(d) = √(K(d) / π) @@ -241,36 +294,42 @@ void calculate_l_function(struct Point *points, int n, const char *output_file) * Calculates the f function for a given array of points. * F(d) = (1/n) * Σ I(d_ij ≤ d) * + * @param kdtree The k-d tree used for nearest neighbor search. * @param points The array of points. * @param n The number of points in the array. * @param output_file The output file to write the results to. + * @param num_random_points The number of random points to generate. */ -void calculate_f_function(struct Point *points, int n, const char *output_file) +void calculate_f_function(struct kdtree *kdtree, struct Point *points, int n, + const char *output_file, struct bound_box *box, + int num_random_points) { FILE *fp = fopen(output_file, "w"); if (fp == NULL) { G_fatal_error(_("Unable to open output file <%s>"), output_file); } - double *distances = (double *)malloc(n * sizeof(double)); + // Generate random points + struct Point *random_points = + (struct Point *)malloc(num_random_points * sizeof(struct Point)); + + generate_random_points(random_points, num_random_points, box); + double max_dist = 0.0; + double *distances = (double *)malloc(num_random_points * sizeof(double)); + #pragma omp parallel for reduction(max : max_dist) - for (int i = 0; i < n; i++) { - double min_dist = DBL_MAX; - for (int j = 0; j < n; j++) { - if (i != j) { - double dist = euclidean_distance(&points[i], &points[j]); - if (dist < min_dist) { - min_dist = dist; - } - if (dist > max_dist) { - max_dist = dist; - } - } + for (int i = 0; i < num_random_points; i++) { + double coords[2] = {random_points[i].x, random_points[i].y}; + int puid; + double pd; + kdtree_knn(kdtree, coords, &puid, &pd, 1, NULL); + distances[i] = sqrt(pd); + if (distances[i] > max_dist) { + max_dist = distances[i]; } - distances[i] = sqrt(min_dist); } - max_dist = sqrt(max_dist); + G_message(_("Max distance: %f"), max_dist); fprintf(fp, "Distance,F-value\n"); @@ -278,16 +337,17 @@ void calculate_f_function(struct Point *points, int n, const char *output_file) double f_value = 0.0; #pragma omp parallel for reduction(+ : f_value) - for (int i = 0; i < n; i++) { + for (int i = 0; i < num_random_points; i++) { if (distances[i] <= d) { f_value += 1; } } - f_value /= n; + f_value /= num_random_points; fprintf(fp, "%f,%f\n", d, f_value); } + free(random_points); free(distances); fclose(fp); } From 6d6870199ebb3a3aa68456d310112d67a1f31dba Mon Sep 17 00:00:00 2001 From: Corey White Date: Wed, 26 Jun 2024 13:21:06 -0400 Subject: [PATCH 12/18] updated g-function to return g-value under csr --- vector/v.ppa/main.c | 85 +++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index 081c791a7e3..dabb4dc5033 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -18,7 +18,7 @@ struct Point { }; void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, - const char *output_file); + int i, const char *output_file); void calculate_f_function(struct kdtree *kdtree, struct Point *points, int n, const char *output_file, struct bound_box *box, int num_random_points); @@ -29,6 +29,7 @@ void calculate_l_function(struct Point *points, int n, const char *output_file); double euclidean_distance(const struct Point *p1, const struct Point *p2); void generate_random_points(struct Point *random_points, int num_random_points, struct bound_box *box); +double csr_g_value(double d, double i); int main(int argc, char *argv[]) { @@ -124,6 +125,14 @@ int main(int argc, char *argv[]) } } + // Get bounding box + struct bound_box box; + Vect_get_map_box(&Map, &box); + // Calculate mean number of points per unit area (intensity) + double area = (box.E - box.W) * (box.N - box.S); + double intensity = n / area; + G_message(_("intensity: %f"), intensity); + G_message(_("Number of points: %d"), n); G_message(_("Method: %s"), method); @@ -134,14 +143,11 @@ int main(int argc, char *argv[]) calculate_l_function(pts, n, output_file); } else if (strcmp(method, "f") == 0) { - // Get bounding box - struct bound_box box; - Vect_get_map_box(&Map, &box); calculate_f_function(kdtree, pts, n, output_file, &box, num_random_points); } else if (strcmp(method, "g") == 0) { - calculate_g_function(kdtree, pts, n, output_file); + calculate_g_function(kdtree, pts, n, intensity, output_file); } else { G_fatal_error(_("Method not implemented yet")); @@ -227,6 +233,19 @@ void generate_random_points(struct Point *random_points, int num_random_points, } } +/** + * Calculates the g function for a given array of points. + * G(d) = 1 - exp(-i * π * d^2) + * + * @param d The distance. + * @param i The intensity (points per unit area). + * @return The g value. + */ +double csr_g_value(double d, double i) +{ + return 1 - exp(-i * M_PI * d * d); +} + /** * Calculates the L function for a given array of points. * L(d) = √(K(d) / π) @@ -352,17 +371,8 @@ void calculate_f_function(struct kdtree *kdtree, struct Point *points, int n, fclose(fp); } -/** - * Calculates the g function using a k-d tree and an array of points. - * - * @param kdtree The k-d tree used for nearest neighbor search. - * @param points An array of points. - * @param n The number of points in the array. - * @param output_file The path to the output file where the results will be - * written. - */ void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, - const char *output_file) + int i, const char *output_file) { G_message(_("G-Function")); FILE *fp = fopen(output_file, "w"); @@ -371,39 +381,40 @@ void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, } double max_dist = 0.0; + double *nearest_distances = (double *)malloc(n * sizeof(double)); + #pragma omp parallel for reduction(max : max_dist) for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - if (i != j) { - double dist = euclidean_distance(&points[i], &points[j]); - if (dist > max_dist) { - max_dist = dist; - } - } + double coords[2] = {points[i].x, points[i].y}; + int puid = 0; + double pd = 0.0; + kdtree_knn( + kdtree, coords, &puid, &pd, 2, + &points[i] + .id); // Find the nearest neighbor excluding the point itself + + nearest_distances[i] = sqrt(pd); + if (nearest_distances[i] > max_dist) { + max_dist = nearest_distances[i]; } } - max_dist = sqrt(max_dist); - fprintf(fp, "Distance,G-value\n"); - for (double d = 0; d <= max_dist; d += max_dist / 100) { - G_percent(d, max_dist, 4); + G_message(_("Max distance: %f"), max_dist); + double g_value_csr = 0.0; + fprintf(fp, "Distance,G-value,G-value-CSR\n"); + for (double d = 0.0; d <= max_dist; d += max_dist / 100) { double g_value = 0.0; #pragma omp parallel for reduction(+ : g_value) for (int i = 0; i < n; i++) { - double coords[2] = {points[i].x, points[i].y}; - int puid = 0; - double pd = 0.0; - kdtree_knn(kdtree, coords, &puid, &pd, 1, &points[i].id); - - if (pd <= d) { - g_value += 1.0; // add the distance to the nearest neighbor + if (nearest_distances[i] <= d) { + g_value += 1.0; } } - - // Normalize g-value - g_value /= n; - fprintf(fp, "%f,%f\n", d, g_value); + g_value_csr = csr_g_value(d, i); + g_value /= n; // Normalize g-value + fprintf(fp, "%f,%f,%f\n", d, g_value, g_value_csr); } + free(nearest_distances); fclose(fp); } From 94f52988cbe7e28a0fe674a28e1c6d0f91b7e0cd Mon Sep 17 00:00:00 2001 From: Corey White Date: Wed, 26 Jun 2024 13:24:22 -0400 Subject: [PATCH 13/18] updated g-function docs --- vector/v.ppa/main.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index dabb4dc5033..d39e6fc7826 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -371,6 +371,17 @@ void calculate_f_function(struct kdtree *kdtree, struct Point *points, int n, fclose(fp); } +/** + * Calculates the G-Function for a given set of points and writes the results to + * an output file. + * + * @param kdtree The k-d tree data structure used for nearest neighbor search. + * @param points An array of Point structures representing the input points. + * @param n The number of points in the input array. + * @param i The intensity of the point being processed. + * @param output_file The path to the output file where the results will be + * written. + */ void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, int i, const char *output_file) { From 2763ef1a9a1c6869756444bc43988f9d213d6462 Mon Sep 17 00:00:00 2001 From: Corey White Date: Wed, 26 Jun 2024 17:24:59 -0400 Subject: [PATCH 14/18] Updated K and L functions and MC Sim --- vector/v.ppa/main.c | 396 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 315 insertions(+), 81 deletions(-) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index d39e6fc7826..c76ebfe5e1b 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -19,18 +19,41 @@ struct Point { void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, int i, const char *output_file); +void calculate_g_function_values(struct kdtree *kdtree, struct Point *points, + int n, int num_distances, double *values); void calculate_f_function(struct kdtree *kdtree, struct Point *points, int n, const char *output_file, struct bound_box *box, int num_random_points); +void calculate_f_function_values(struct kdtree *kdtree, struct Point *points, + int n, struct bound_box *box, double *values, + int num_random_points); + void calculate_k_function(struct kdtree *kdtree, struct Point *points, int n, + int num_distances, double intensity, const char *output_file); -void calculate_l_function(struct Point *points, int n, const char *output_file); - +void calculate_l_function(struct kdtree *kdtree, struct Point *points, int n, + int num_distances, double intensity, + const char *output_file); +void calculate_k_function_values(struct kdtree *kdtree, struct Point *points, + int n, int num_distances, double max_dist, + double intensity, double *values); +void calculate_l_function_values(struct kdtree *kdtree, struct Point *points, + int n, int num_distances, double max_dist, + double intensity, double *values); +double max_distance(struct Point *points, int n); double euclidean_distance(const struct Point *p1, const struct Point *p2); void generate_random_points(struct Point *random_points, int num_random_points, struct bound_box *box); double csr_g_value(double d, double i); - +void calculate_function_values( + struct kdtree *kdtree, struct Point *points, int n, double *values, + int num_distances, double max_dist, + void (*calc_func)(struct kdtree *, struct Point *, int, double *)); +void monte_carlo_envelope(struct kdtree *kdtree, struct Point *points, + struct bound_box *box, int n, const char *output_file, + int num_simulations, + void (*calc_func)(struct kdtree *, struct Point *, + int, double *)); int main(int argc, char *argv[]) { struct Map_info Map; @@ -42,6 +65,8 @@ int main(int argc, char *argv[]) struct GModule *module = G_define_module(); G_add_keyword(_("vector")); G_add_keyword(_("point pattern analysis")); + G_add_keyword(_("parallel")); + G_add_keyword(_("statistics")); module->description = _("Point pattern analysis using G, F, K, and L functions."); @@ -52,7 +77,7 @@ int main(int argc, char *argv[]) method_opt->key = "method"; method_opt->type = TYPE_STRING; method_opt->required = YES; - method_opt->options = "k,l,f,g"; + method_opt->options = "g,f,k,l"; method_opt->description = _("Method to calculate (g, f, k, l)"); struct Option *random_points_opt = G_define_option(); @@ -63,6 +88,21 @@ int main(int argc, char *argv[]) random_points_opt->description = _("Number of random points for F-function calculation (default: 1000)"); + struct Option *num_distances_opt = G_define_option(); + num_distances_opt->key = "num_distances"; + num_distances_opt->type = TYPE_INTEGER; + num_distances_opt->required = NO; + num_distances_opt->answer = "100"; + num_distances_opt->description = _("Number of distances (default: 100)"); + + struct Option *simulations_opt = G_define_option(); + simulations_opt->key = "simulations"; + simulations_opt->type = TYPE_INTEGER; + simulations_opt->required = NO; + simulations_opt->answer = "99"; + simulations_opt->description = + _("Number of simulations for Monte Carlo envelope (default: 99)"); + struct Option *random_seed = G_define_option(); random_seed->key = "seed"; random_seed->type = TYPE_INTEGER; @@ -94,6 +134,8 @@ int main(int argc, char *argv[]) const char *output_file = output_opt->answer; const char *method = method_opt->answer; int num_random_points = atoi(random_points_opt->answer); + int num_distances = atoi(num_distances_opt->answer); + int num_simulations = atoi(simulations_opt->answer); // Open the vector map if (Vect_open_old(&Map, input_vector, "") < 0) @@ -130,17 +172,19 @@ int main(int argc, char *argv[]) Vect_get_map_box(&Map, &box); // Calculate mean number of points per unit area (intensity) double area = (box.E - box.W) * (box.N - box.S); - double intensity = n / area; + double intensity = (double)n / area; G_message(_("intensity: %f"), intensity); G_message(_("Number of points: %d"), n); G_message(_("Method: %s"), method); if (strcmp(method, "k") == 0) { - calculate_k_function(kdtree, pts, n, output_file); + calculate_k_function(kdtree, pts, n, num_distances, intensity, + output_file); } else if (strcmp(method, "l") == 0) { - calculate_l_function(pts, n, output_file); + calculate_l_function(kdtree, pts, n, num_distances, intensity, + output_file); } else if (strcmp(method, "f") == 0) { calculate_f_function(kdtree, pts, n, output_file, &box, @@ -161,59 +205,107 @@ int main(int argc, char *argv[]) return 0; } +double max_distance(struct Point *points, int n) +{ + double max_dist = 0.0; +#pragma omp parallel for reduction(max : max_dist) + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j) { + double dist = euclidean_distance(&points[i], &points[j]); + if (dist > max_dist) { + max_dist = dist; + } + } + } + } + max_dist = sqrt(max_dist); + return max_dist; +} + +void calculate_function_values(struct kdtree *kdtree, struct Point *points, + int n, double *values, int num_distances, + double max_dist, + void (*calc_func)(struct kdtree *, + struct Point *, int, double *)) +{ + calc_func(kdtree, points, n, values); +} + /** - * Calculates the k-function for a given set of points using a k-d tree. + * Calculates the F function for a given set of points and generates an envelope + * using Monte Carlo simulations. * * @param kdtree The k-d tree used for nearest neighbor searches. * @param points An array of Point structures representing the points. + * @param box The bounding box of the map. * @param n The number of points in the array. * @param output_file The path to the output file where the results will be * saved. + * @param num_random_points The number of random points to generate for F + * function calculation. + * @param num_simulations The number of Monte Carlo simulations to perform. */ -void calculate_k_function(struct kdtree *kdtree, struct Point *points, int n, - const char *output_file) +void monte_carlo_envelope(struct kdtree *kdtree, struct Point *points, + struct bound_box *box, int n, const char *output_file, + int num_simulations, + void (*calc_func)(struct kdtree *, struct Point *, + int, double *)) { FILE *fp = fopen(output_file, "w"); if (fp == NULL) { G_fatal_error(_("Unable to open output file <%s>"), output_file); } - double max_dist = 0.0; -#pragma omp parallel for reduction(max : max_dist) - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - if (i != j) { - double dist = euclidean_distance(&points[i], &points[j]); - if (dist > max_dist) { - max_dist = dist; - } - } - } - } + struct Point *random_points = + (struct Point *)malloc(n * sizeof(struct Point)); - G_message(_("Max distance: %f"), max_dist); + double max_dist = max_distance(points, n); - fprintf(fp, "Distance,K-value\n"); - for (double d = 0; d <= max_dist; d += max_dist / 100) { - double k_value = 0.0; + int num_distances = 100; + double *values = (double *)malloc(num_distances * sizeof(double)); + double *lower_envelope = (double *)malloc(num_distances * sizeof(double)); + double *upper_envelope = (double *)malloc(num_distances * sizeof(double)); -#pragma omp parallel for reduction(+ : k_value) - for (int i = 0; i < n; i++) { - double coords[2] = {points[i].x, points[i].y}; - int *puid = NULL; - double *pd = NULL; - int count = - kdtree_dnn(kdtree, coords, &puid, &pd, d, &points[i].id); - k_value += count - 1; // subtract 1 to exclude the point itself - free(puid); - free(pd); + for (int d = 0; d < num_distances; d++) { + lower_envelope[d] = DBL_MAX; + upper_envelope[d] = DBL_MIN; + } + +#pragma omp parallel for + for (int sim = 0; sim < num_simulations; sim++) { + generate_random_points( + random_points, n, + box); // Generate same number of random points as observed points + double *sim_values = (double *)malloc(num_distances * sizeof(double)); + calculate_function_values(kdtree, random_points, n, sim_values, + num_distances, max_dist, calc_func); + +#pragma omp critical + { + for (int d = 0; d < num_distances; d++) { + if (sim_values[d] < lower_envelope[d]) { + lower_envelope[d] = sim_values[d]; + } + if (sim_values[d] > upper_envelope[d]) { + upper_envelope[d] = sim_values[d]; + } + } } + free(sim_values); + } - k_value /= (n * (n - 1)); - fprintf(fp, "%f,%f\n", d, k_value); + fprintf(fp, "Distance,Lower Envelope,Upper Envelope\n"); + for (int d = 0; d < num_distances; d++) { + double distance = d * max_dist / num_distances; + fprintf(fp, "%f,%f,%f\n", distance, lower_envelope[d], + upper_envelope[d]); } - kdtree_destroy(kdtree); + free(random_points); + free(values); + free(lower_envelope); + free(upper_envelope); fclose(fp); } @@ -255,63 +347,32 @@ double csr_g_value(double d, double i) * @param output_file The path to the output file where the results will be * written. */ -void calculate_l_function(struct Point *points, int n, const char *output_file) +void calculate_l_function(struct kdtree *kdtree, struct Point *points, int n, + int num_distances, double intensity, + const char *output_file) { FILE *fp = fopen(output_file, "w"); if (fp == NULL) { G_fatal_error(_("Unable to open output file <%s>"), output_file); } - - double **distances = (double **)malloc(n * sizeof(double *)); - for (int i = 0; i < n; i++) { - distances[i] = (double *)malloc(n * sizeof(double)); - } - - double max_dist = 0.0; -#pragma omp parallel for reduction(max : max_dist) - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - if (i != j) { - double dist = euclidean_distance(&points[i], &points[j]); - distances[i][j] = dist; - if (dist > max_dist) { - max_dist = dist; - } - } - } - } - max_dist = sqrt(max_dist); - G_message(_("Max distance: %f"), max_dist); + double max_dist = max_distance(points, n); + double interval = max_dist / num_distances; + double *values = (double *)malloc(num_distances * sizeof(double)); + calculate_l_function_values(kdtree, points, n, num_distances, max_dist, + intensity, values); fprintf(fp, "Distance,L-value\n"); - for (double d = 0.0; d <= max_dist; d += max_dist / 100.0) { - double k_value = 0.0; - -#pragma omp parallel for reduction(+ : k_value) - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - if (i != j && distances[i][j] <= d) { - k_value += 1.0; - } - } - } - - k_value /= (n * (n - 1)); - double l_value = sqrt(k_value / M_PI); - fprintf(fp, "%f,%f\n", d, l_value); + for (int d = 0; d < num_distances; d++) { + fprintf(fp, "%f,%f\n", d * interval, values[d]); } - for (int i = 0; i < n; i++) { - free(distances[i]); - } - free(distances); + free(values); fclose(fp); } /** * Calculates the f function for a given array of points. - * F(d) = (1/n) * Σ I(d_ij ≤ d) * * @param kdtree The k-d tree used for nearest neighbor search. * @param points The array of points. @@ -429,3 +490,176 @@ void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, free(nearest_distances); fclose(fp); } + +void calculate_k_function(struct kdtree *kdtree, struct Point *points, int n, + int num_distances, double intensity, + const char *output_file) +{ + FILE *fp = fopen(output_file, "w"); + if (fp == NULL) { + G_fatal_error(_("Unable to open output file <%s>"), output_file); + } + + double max_dist = max_distance(points, n); + double interval = max_dist / num_distances; + G_message(_("Max distance: %f"), max_dist); + + double *values = (double *)malloc(num_distances * sizeof(double)); + calculate_k_function_values(kdtree, points, n, num_distances, max_dist, + intensity, values); + + fprintf(fp, "Distance,K-value\n"); + for (int d = 0; d < num_distances; d++) { + fprintf(fp, "%f,%f\n", d * interval, values[d]); + } + + free(values); + fclose(fp); +} + +void calculate_g_function_values(struct kdtree *kdtree, struct Point *points, + int n, int num_distances, double *values) +{ + double max_dist = 0.0; + double *nearest_distances = (double *)malloc(n * sizeof(double)); + +// Calculate the nearest neighbor distances for each point and find the maximum +// distance +#pragma omp parallel for reduction(max : max_dist) + for (int i = 0; i < n; i++) { + double coords[2] = {points[i].x, points[i].y}; + int puid = 0; + double pd = 0.0; + kdtree_knn( + kdtree, coords, &puid, &pd, 2, + &points[i] + .id); // Find the nearest neighbor excluding the point itself + + nearest_distances[i] = sqrt(pd); + if (nearest_distances[i] > max_dist) { + max_dist = nearest_distances[i]; + } + } + + double interval = max_dist / num_distances; + + // Initialize the G-values array + for (int d = 0; d < num_distances; d++) { + values[d] = 0.0; + } + + // Calculate G-values for the specified distances + for (int d = 0; d < num_distances; d++) { + double dist = d * interval; + double g_value = 0.0; + +#pragma omp parallel for reduction(+ : g_value) + for (int i = 0; i < n; i++) { + if (nearest_distances[i] <= dist) { + g_value += 1.0; + } + } + values[d] = g_value / n; + } + + free(nearest_distances); +} + +// You need to implement these placeholder functions +void calculate_k_function_values(struct kdtree *kdtree, struct Point *points, + int n, int num_distances, double max_dist, + double intensity, double *values) +{ + + double interval = max_dist / num_distances; + + G_percent(0, num_distances, 1); +#pragma omp parallel for + for (int d = 0; d <= num_distances; d++) { + double k_value = 0.0; + double radius = d * interval; + +#pragma omp parallel for reduction(+ : k_value) + for (int i = 0; i < n; i++) { + double coords[2] = {points[i].x, points[i].y}; + int count; + int *puid = NULL; + double *pd = NULL; + count = + kdtree_dnn(kdtree, coords, &puid, &pd, radius, &points[i].id); + + if (count > 0) { + k_value += count - 1; // subtract 1 to exclude the point itself + } + + free(puid); + free(pd); + } + + k_value /= (n * intensity); + values[d] = k_value; + + // Update progress indicator + G_percent(d, num_distances, 1); + } + + // Finalize the progress indicator + G_percent(num_distances, num_distances, 1); +} + +void calculate_l_function_values(struct kdtree *kdtree, struct Point *points, + int n, int num_distances, double max_dist, + double intensity, double *values) +{ + + calculate_k_function_values(kdtree, points, n, num_distances, max_dist, + intensity, values); +#pragma omp parallel for + for (int i = 0; i < n; i++) { + values[i] = sqrt(values[i] / M_PI); + } +} + +void calculate_f_function_values(struct kdtree *kdtree, struct Point *points, + int n, struct bound_box *box, double *values, + int num_random_points) +{ + + // Generate random points + struct Point *random_points = + (struct Point *)malloc(num_random_points * sizeof(struct Point)); + + generate_random_points(random_points, num_random_points, box); + + double max_dist = 0.0; + double *distances = (double *)malloc(num_random_points * sizeof(double)); + +#pragma omp parallel for reduction(max : max_dist) + for (int i = 0; i < num_random_points; i++) { + double coords[2] = {random_points[i].x, random_points[i].y}; + int puid; + double pd; + kdtree_knn(kdtree, coords, &puid, &pd, 1, NULL); + distances[i] = sqrt(pd); + if (distances[i] > max_dist) { + max_dist = distances[i]; + } + } + + for (int d = 0.0; d <= max_dist; d += max_dist / 100.0) { + double f_value = 0.0; + +#pragma omp parallel for reduction(+ : f_value) + for (int i = 0; i < num_random_points; i++) { + if (distances[i] <= d) { + f_value += 1; + } + } + + f_value /= num_random_points; + values[d] = f_value; + } + + free(random_points); + free(distances); +} From abe3a202738abeec87864e15815797873acd911d Mon Sep 17 00:00:00 2001 From: Corey White Date: Tue, 9 Jul 2024 14:24:48 -0400 Subject: [PATCH 15/18] Added base setup for JSON support --- vector/Makefile | 1 + vector/v.ppa/Makefile | 2 +- vector/v.ppa/main.c | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/vector/Makefile b/vector/Makefile index f195ce3aafb..06d13f45093 100644 --- a/vector/Makefile +++ b/vector/Makefile @@ -40,6 +40,7 @@ SUBDIRS = \ v.lidar.growing \ v.lrs \ v.proj \ + v.ppa \ v.mkgrid \ v.neighbors \ v.net \ diff --git a/vector/v.ppa/Makefile b/vector/v.ppa/Makefile index a4cf9b1faee..32b1b0bbd84 100644 --- a/vector/v.ppa/Makefile +++ b/vector/v.ppa/Makefile @@ -3,7 +3,7 @@ MODULE_TOPDIR = ../.. PGM=v.ppa -LIBES = $(VECTORLIB) $(DBMILIB) $(BTREE2LIB) $(GISLIB) +LIBES = $(VECTORLIB) $(DBMILIB) $(BTREE2LIB) $(GISLIB) $(PARSONLIB) EXTRA_LIBS = $(OPENMP_LIBPATH) $(OPENMP_LIB) DEPENDENCIES = $(VECTORDEP) $(DBMIDEP) $(BTREE2DEP) $(GISDEP) EXTRA_INC = $(VECT_INC) $(OPENMP_INCPATH) diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index c76ebfe5e1b..bec6562d4db 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -11,12 +11,15 @@ #include #include #include +#include struct Point { double x, y; int id; }; +enum OutputFormat { PLAIN, JSON }; + void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, int i, const char *output_file); void calculate_g_function_values(struct kdtree *kdtree, struct Point *points, @@ -88,6 +91,9 @@ int main(int argc, char *argv[]) random_points_opt->description = _("Number of random points for F-function calculation (default: 1000)"); + struct Option *format_opt = G_define_standard_option(G_OPT_F_FORMAT); + format_opt->required = NO; + struct Option *num_distances_opt = G_define_option(); num_distances_opt->key = "num_distances"; num_distances_opt->type = TYPE_INTEGER; @@ -133,6 +139,7 @@ int main(int argc, char *argv[]) const char *input_vector = input_opt->answer; const char *output_file = output_opt->answer; const char *method = method_opt->answer; + int num_random_points = atoi(random_points_opt->answer); int num_distances = atoi(num_distances_opt->answer); int num_simulations = atoi(simulations_opt->answer); @@ -141,6 +148,22 @@ int main(int argc, char *argv[]) if (Vect_open_old(&Map, input_vector, "") < 0) G_fatal_error(_("Unable to open vector map <%s>"), input_vector); + enum OutputFormat format; + JSON_Value *root_value; + JSON_Object *root_object; + + if (strcmp(format_opt->answer, "json") == 0) { + format = JSON; + root_value = json_value_init_object(); + if (root_value == NULL) { + G_fatal_error(_("Unable to initialize JSON object")); + } + root_object = json_object(root_value); + } + else { + format = PLAIN; + } + // Allocate memory for points struct line_pnts *points = Vect_new_line_struct(); struct line_cats *cats = Vect_new_cats_struct(); @@ -202,6 +225,17 @@ int main(int argc, char *argv[]) kdtree_destroy(kdtree); // Ensure k-d tree is destroyed Vect_close(&Map); + if (format == JSON) { + char *serialized_string = NULL; + serialized_string = json_serialize_to_string_pretty(root_value); + if (serialized_string == NULL) { + G_fatal_error(_("Failed to initialize pretty JSON string.")); + } + puts(serialized_string); + json_free_serialized_string(serialized_string); + json_value_free(root_value); + } + return 0; } From eaab3ae50f1627b56cdaf86fa10e5f6f00baa72a Mon Sep 17 00:00:00 2001 From: Corey White Date: Thu, 12 Sep 2024 12:55:50 -0400 Subject: [PATCH 16/18] Added tests --- vector/v.ppa/testsuite/test_v_ppa.py | 170 +++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/vector/v.ppa/testsuite/test_v_ppa.py b/vector/v.ppa/testsuite/test_v_ppa.py index e69de29bb2d..b22f446fb49 100644 --- a/vector/v.ppa/testsuite/test_v_ppa.py +++ b/vector/v.ppa/testsuite/test_v_ppa.py @@ -0,0 +1,170 @@ +""" +Name: v.ppa test +Purpose: Tests v.ppa functions. +Author: Corey T. White +Copyright: (C) 2024 by Corey White and the GRASS Development Team +Licence: This program is free software under the GNU General Public + License (>=v2). Read the file COPYING that comes with GRASS + for details. +""" + +from grass.gunittest.case import TestCase + + +class TestPointPatternAnalysis(TestCase): + input = "firestations" + + @classmethod + def setUpClass(cls): + cls.use_temp_region() + + @classmethod + def tearDownClass(cls): + cls.del_temp_region() + + def tearDown(cls): + pass + # cls.runModule("g.remove", type="vector", flags="f", name=cls.output) + + def test_g_function_csv(self): + """Testing g function csv output""" + self.assertModule( + "v.ppa", + input=self.input, + output=f"outputs/g_{self.input}.csv", + method="g", + format="plain", + seed=1, + overwrite=True, + ) + + def test_g_function_json(self): + """Testing g function json output""" + self.assertModule( + "v.ppa", + input=self.input, + output=f"outputs/g_{self.input}.json", + method="g", + format="json", + seed=1, + overwrite=True, + ) + + def test_f_function_plain(self): + """Testing f function csv output""" + self.assertModule( + "v.ppa", + input=self.input, + output=f"outputs/f_{self.input}.csv", + method="f", + format="plain", + seed=1, + overwrite=True, + ) + + def test_f_function_json(self): + """Testing f function json output""" + self.assertModule( + "v.ppa", + input=self.input, + output=f"outputs/f_{self.input}.json", + method="f", + format="json", + seed=1, + overwrite=True, + ) + + def test_f_function_num_distances(self): + """Testing f function num_distance parameter""" + self.assertModule( + "v.ppa", + input=self.input, + output=f"outputs/g_{self.input}.json", + method="f", + format="json", + num_distances=400, + seed=1, + overwrite=True, + ) + + def test_f_function_random_points(self): + """Testing f function random points parameter""" + self.assertModule( + "v.ppa", + input=self.input, + output=f"outputs/g_{self.input}.json", + method="f", + format="json", + random_points=2000, + seed=1, + overwrite=True, + ) + + def test_k_function_plain(self): + """Testing k function csv output""" + self.assertModule( + "v.ppa", + input=self.input, + output=f"outputs/k_{self.input}.csv", + method="k", + format="plain", + seed=1, + overwrite=True, + ) + + def test_k_function_json(self): + """Testing k function json output""" + self.assertModule( + "v.ppa", + input=self.input, + output=f"outputs/k_{self.input}.json", + method="k", + format="json", + seed=1, + overwrite=True, + ) + + def test_l_function_plain(self): + """Testing l function csv output""" + self.assertModule( + "v.ppa", + input=self.input, + output=f"outputs/l_{self.input}.csv", + method="l", + format="plain", + seed=1, + overwrite=True, + ) + + def test_l_function_json(self): + """Testing l function json output""" + self.assertModule( + "v.ppa", + input=self.input, + output=f"outputs/l_{self.input}.json", + method="l", + format="json", + seed=1, + overwrite=True, + ) + + def test_simulation(self): + pass + + def test_monte_carlo_envelope(self): + pass + + def test_generate_random_points(self): + pass + + def test_euclidean_distance(self): + pass + + def test_max_distance(self): + pass + + +if __name__ == "__main__": + from grass.gunittest.main import test + + test() From f699933890715b4485cd596879fb0e4f59f67b15 Mon Sep 17 00:00:00 2001 From: Corey White Date: Thu, 3 Oct 2024 15:59:52 -0400 Subject: [PATCH 17/18] Added json output to k and l functions --- vector/v.ppa/main.c | 126 ++++++++--- vector/v.ppa/testsuite/README.md | 12 + .../data/raleigh_crime_2022_2024.fgb | Bin 0 -> 59808 bytes vector/v.ppa/testsuite/f_crashes.csv | 102 --------- vector/v.ppa/testsuite/g_crashes.csv | 102 --------- vector/v.ppa/testsuite/k_crashes.csv | 102 --------- vector/v.ppa/testsuite/l_crashes.csv | 102 --------- vector/v.ppa/testsuite/outputs/f_crime.csv | 102 +++++++++ vector/v.ppa/testsuite/outputs/f_crime.json | 1 + vector/v.ppa/testsuite/outputs/g_crime.csv | 101 +++++++++ vector/v.ppa/testsuite/outputs/g_crime.json | 1 + vector/v.ppa/testsuite/outputs/k_crime.csv | 101 +++++++++ vector/v.ppa/testsuite/outputs/k_crime.json | 206 ++++++++++++++++++ vector/v.ppa/testsuite/outputs/l_crime.csv | 101 +++++++++ vector/v.ppa/testsuite/outputs/l_crime.json | 206 ++++++++++++++++++ vector/v.ppa/testsuite/test_v_ppa.py | 25 ++- 16 files changed, 951 insertions(+), 439 deletions(-) create mode 100644 vector/v.ppa/testsuite/README.md create mode 100644 vector/v.ppa/testsuite/data/raleigh_crime_2022_2024.fgb delete mode 100644 vector/v.ppa/testsuite/f_crashes.csv delete mode 100644 vector/v.ppa/testsuite/g_crashes.csv delete mode 100644 vector/v.ppa/testsuite/k_crashes.csv delete mode 100644 vector/v.ppa/testsuite/l_crashes.csv create mode 100644 vector/v.ppa/testsuite/outputs/f_crime.csv create mode 100644 vector/v.ppa/testsuite/outputs/f_crime.json create mode 100644 vector/v.ppa/testsuite/outputs/g_crime.csv create mode 100644 vector/v.ppa/testsuite/outputs/g_crime.json create mode 100644 vector/v.ppa/testsuite/outputs/k_crime.csv create mode 100644 vector/v.ppa/testsuite/outputs/k_crime.json create mode 100644 vector/v.ppa/testsuite/outputs/l_crime.csv create mode 100644 vector/v.ppa/testsuite/outputs/l_crime.json diff --git a/vector/v.ppa/main.c b/vector/v.ppa/main.c index bec6562d4db..0b71cf12397 100644 --- a/vector/v.ppa/main.c +++ b/vector/v.ppa/main.c @@ -33,10 +33,12 @@ void calculate_f_function_values(struct kdtree *kdtree, struct Point *points, void calculate_k_function(struct kdtree *kdtree, struct Point *points, int n, int num_distances, double intensity, - const char *output_file); + const char *output_file, enum OutputFormat format, + JSON_Object *root_object); void calculate_l_function(struct kdtree *kdtree, struct Point *points, int n, int num_distances, double intensity, - const char *output_file); + const char *output_file, enum OutputFormat format, + JSON_Object *root_object); void calculate_k_function_values(struct kdtree *kdtree, struct Point *points, int n, int num_distances, double max_dist, double intensity, double *values); @@ -76,6 +78,8 @@ int main(int argc, char *argv[]) // Define options struct Option *input_opt = G_define_standard_option(G_OPT_V_INPUT); struct Option *output_opt = G_define_standard_option(G_OPT_F_OUTPUT); + output_opt->required = NO; + struct Option *method_opt = G_define_option(); method_opt->key = "method"; method_opt->type = TYPE_STRING; @@ -203,11 +207,11 @@ int main(int argc, char *argv[]) if (strcmp(method, "k") == 0) { calculate_k_function(kdtree, pts, n, num_distances, intensity, - output_file); + output_file, format, root_object); } else if (strcmp(method, "l") == 0) { calculate_l_function(kdtree, pts, n, num_distances, intensity, - output_file); + output_file, format, root_object); } else if (strcmp(method, "f") == 0) { calculate_f_function(kdtree, pts, n, output_file, &box, @@ -232,6 +236,15 @@ int main(int argc, char *argv[]) G_fatal_error(_("Failed to initialize pretty JSON string.")); } puts(serialized_string); + if (output_file) { + FILE *fp = fopen(output_file, "w"); + if (fp == NULL) { + G_fatal_error(_("Unable to open output file <%s>"), + output_file); + } + fputs(serialized_string, fp); + fclose(fp); + } json_free_serialized_string(serialized_string); json_value_free(root_value); } @@ -383,26 +396,58 @@ double csr_g_value(double d, double i) */ void calculate_l_function(struct kdtree *kdtree, struct Point *points, int n, int num_distances, double intensity, - const char *output_file) + const char *output_file, enum OutputFormat format, + JSON_Object *root_object) { - - FILE *fp = fopen(output_file, "w"); - if (fp == NULL) { - G_fatal_error(_("Unable to open output file <%s>"), output_file); - } + FILE *fp = NULL; double max_dist = max_distance(points, n); double interval = max_dist / num_distances; double *values = (double *)malloc(num_distances * sizeof(double)); + if (values == NULL) { + G_fatal_error(_("Unable to allocate memory for values array.")); + } calculate_l_function_values(kdtree, points, n, num_distances, max_dist, intensity, values); - fprintf(fp, "Distance,L-value\n"); - for (int d = 0; d < num_distances; d++) { - fprintf(fp, "%f,%f\n", d * interval, values[d]); - } + switch (format) { + case PLAIN: + fp = fopen(output_file, "w"); + if (fp == NULL) { + G_fatal_error(_("Unable to open output file <%s>"), output_file); + } + + fprintf(fp, "Distance,L-value\n"); + for (int d = 0; d < num_distances; d++) { + fprintf(fp, "%f,%f\n", d * interval, values[d]); + } + fclose(fp); + break; + case JSON: + if (root_object == NULL) { + G_fatal_error(_("root_object is NULL.")); + } + json_object_set_null(root_object, "distance"); + json_object_set_null(root_object, "l-value"); + JSON_Value *distance = json_value_init_array(); + if (distance == NULL) { + G_fatal_error(_("Unable to initialize JSON distance array.")); + } + JSON_Array *distances = json_array(distance); + JSON_Value *l_value = json_value_init_array(); + if (l_value == NULL) { + G_fatal_error(_("Unable to initialize JSON l-value array.")); + } + JSON_Array *l_values = json_array(l_value); + for (int d = 0; d < num_distances; d++) { + json_array_append_number(distances, d * interval); + json_array_append_number(l_values, values[d]); + } + json_object_set_value(root_object, "distance", distance); + json_object_set_value(root_object, "l-value", l_value); + break; + } free(values); - fclose(fp); } /** @@ -527,28 +572,51 @@ void calculate_g_function(struct kdtree *kdtree, struct Point *points, int n, void calculate_k_function(struct kdtree *kdtree, struct Point *points, int n, int num_distances, double intensity, - const char *output_file) + const char *output_file, enum OutputFormat format, + JSON_Object *root_object) { - FILE *fp = fopen(output_file, "w"); - if (fp == NULL) { - G_fatal_error(_("Unable to open output file <%s>"), output_file); - } - double max_dist = max_distance(points, n); double interval = max_dist / num_distances; G_message(_("Max distance: %f"), max_dist); - double *values = (double *)malloc(num_distances * sizeof(double)); calculate_k_function_values(kdtree, points, n, num_distances, max_dist, intensity, values); - fprintf(fp, "Distance,K-value\n"); - for (int d = 0; d < num_distances; d++) { - fprintf(fp, "%f,%f\n", d * interval, values[d]); - } + switch (format) { + case PLAIN: + FILE *fp = fopen(output_file, "w"); + if (fp == NULL) { + G_fatal_error(_("Unable to open output file <%s>"), output_file); + } + fprintf(fp, "Distance,K-value\n"); + for (int d = 0; d < num_distances; d++) { + fprintf(fp, "%f,%f\n", d * interval, values[d]); + } + fclose(fp); + break; + case JSON: + JSON_Value *distance = json_value_init_array(); + if (distance == NULL) { + G_fatal_error(_("Unable to initialize JSON distance array.")); + } + JSON_Array *distances = json_array(distance); + + JSON_Value *k_value = json_value_init_array(); + if (k_value == NULL) { + G_fatal_error(_("Unable to initialize JSON k-value array.")); + } + JSON_Array *k_values = json_array(k_value); + + for (int d = 0; d < num_distances; d++) { + json_array_append_number(distances, d * interval); + json_array_append_number(k_values, values[d]); + } + json_object_set_value(root_object, "distance", distance); + json_object_set_value(root_object, "k-value", k_value); + break; + } free(values); - fclose(fp); } void calculate_g_function_values(struct kdtree *kdtree, struct Point *points, @@ -609,7 +677,7 @@ void calculate_k_function_values(struct kdtree *kdtree, struct Point *points, G_percent(0, num_distances, 1); #pragma omp parallel for - for (int d = 0; d <= num_distances; d++) { + for (int d = 0; d < num_distances; d++) { double k_value = 0.0; double radius = d * interval; @@ -649,7 +717,7 @@ void calculate_l_function_values(struct kdtree *kdtree, struct Point *points, calculate_k_function_values(kdtree, points, n, num_distances, max_dist, intensity, values); #pragma omp parallel for - for (int i = 0; i < n; i++) { + for (int i = 0; i < num_distances; i++) { values[i] = sqrt(values[i] / M_PI); } } diff --git a/vector/v.ppa/testsuite/README.md b/vector/v.ppa/testsuite/README.md new file mode 100644 index 00000000000..42d0dd264cb --- /dev/null +++ b/vector/v.ppa/testsuite/README.md @@ -0,0 +1,12 @@ +# Test Data + +Test data was downloaded from the City of Raleigh, NC open data portal. +The data is a subset of the Daily Police Incidents dataset filtered from 2022 - 2024. + +[Daily_Police_Incidents](https://services.arcgis.com/v400IkDOw1ad7Yad/arcgis/rest/services/Daily_Police_Incidents/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=3358&f=json) + +```bash +v.import input=https://services.arcgis.com/v400IkDOw1ad7Yad/arcgis/rest/ \ +services/Daily_Police_Incidents/FeatureServer/0/query?where=1%3D1 \ +&outFields=*&outSR=3358&f=json layer=ESRIJSON output=raleigh_crime_2022_2024 +``` diff --git a/vector/v.ppa/testsuite/data/raleigh_crime_2022_2024.fgb b/vector/v.ppa/testsuite/data/raleigh_crime_2022_2024.fgb new file mode 100644 index 0000000000000000000000000000000000000000..5d0fceb31c905148edc7e1432deec12a5afc9a4e GIT binary patch literal 59808 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 V*1ZPSZruPq`k8NCn%cL;{Qn Date: Tue, 18 Feb 2025 21:51:03 -0500 Subject: [PATCH 18/18] Added markdown to fix build issue --- vector/v.ppa/testsuite/test_v_ppa.py | 3 -- vector/v.ppa/v.ppa.md | 45 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 vector/v.ppa/v.ppa.md diff --git a/vector/v.ppa/testsuite/test_v_ppa.py b/vector/v.ppa/testsuite/test_v_ppa.py index 818acba4c67..49c4864c3c7 100644 --- a/vector/v.ppa/testsuite/test_v_ppa.py +++ b/vector/v.ppa/testsuite/test_v_ppa.py @@ -30,9 +30,6 @@ def tearDownClass(cls): cls.runModule("g.remove", type="vector", flags="f", name=cls.input) cls.del_temp_region() - def tearDown(cls): - pass - def test_g_function_csv(self): """Testing g function csv output""" self.assertModule( diff --git a/vector/v.ppa/v.ppa.md b/vector/v.ppa/v.ppa.md new file mode 100644 index 00000000000..c519caefe41 --- /dev/null +++ b/vector/v.ppa/v.ppa.md @@ -0,0 +1,45 @@ +## Description + +*v.ppa* allows users to perform point pattern analysis with the G, F, K, L, and bivariate Ripley's K functions. + +## Notes + +## Examples + +### G Function + +Determine the distribution of distances from each point to its nearest neighbor to learn if the points are clustered, dispersed, or randomly distributed across the computational region. + +```bash +v.ppa input=crash output=crash_g.csv method=g +``` + +### F Function + +Determine the distribution of the distances from random points in the computational region to the points in the input vector map. This function is to determine if the points are clustered, dispersed, or randomly distributed across the computational region. The output file will contain the distances from the random points to the nearest crash location. + +```bash +v.ppa input=crash output=crash_f.csv method=f +``` + +### K and L Functions + +Determine if the points are clustered, dispersed, or randomly distributed across spatial scales in the computational region. + +```bash +v.ppa input=crash output=crash_k.csv method=k +``` + +The *L-Function* is a transformation of the K-Function that allows for easier interpretation of the results. + +- If L(r) is less than the expected value, the points are dispersed. +- If L(r) is greater than the expected value, the points are clustered. +- If L(r) is equal to the expected value, the points are randomly distributed (Poisson Process). + +```bash +v.ppa input=crash output=crash_l.csv method=l +``` + +## Authors + +Corey T. White, OpenPlains Inc.