Skip to content

Commit

Permalink
r.profile: add JSON support (OSGeo#3872)
Browse files Browse the repository at this point in the history
Use parson to add json output format support to the r.profile module.
  • Loading branch information
kritibirda26 authored Jul 2, 2024
1 parent 16b69e4 commit 7f4aabf
Show file tree
Hide file tree
Showing 6 changed files with 791 additions and 38 deletions.
2 changes: 1 addition & 1 deletion raster/r.profile/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ MODULE_TOPDIR = ../..

PGM = r.profile

LIBES = $(RASTERLIB) $(GISLIB) $(MATHLIB)
LIBES = $(RASTERLIB) $(GISLIB) $(MATHLIB) $(PARSONLIB)
DEPENDENCIES = $(RASTERDEP) $(GISDEP)

include $(MODULE_TOPDIR)/include/Make/Module.make
Expand Down
10 changes: 7 additions & 3 deletions raster/r.profile/local_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <grass/parson.h>
#include <grass/gis.h>
#include <grass/raster.h>

enum OutputFormat { PLAIN, JSON };

/* main.c */
int do_profile(double, double, double, double, int, double, int, int, FILE *,
char *, const char *, double);
char *, const char *, double, enum OutputFormat, char *,
JSON_Array *);

/* read_rast.c */
int read_rast(double, double, double, int, int, RASTER_MAP_TYPE, FILE *,
char *);
int read_rast(double, double, double, int, int, RASTER_MAP_TYPE, FILE *, char *,
enum OutputFormat, char *, JSON_Array *);

/* input.c */
int input(char *, char *, char *, char *, char *, FILE *);
Expand Down
72 changes: 51 additions & 21 deletions raster/r.profile/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

#include <stdlib.h>
#include <grass/parson.h>
#include <grass/gis.h>
#include <grass/raster.h>
#include <grass/glocale.h>
Expand Down Expand Up @@ -39,10 +40,13 @@ int main(int argc, char *argv[])
struct Cell_head window;
struct {
struct Option *opt1, *profile, *res, *output, *null_str, *coord_file,
*units;
*units, *format;
struct Flag *g, *c, *m;
} parm;
struct GModule *module;
enum OutputFormat format;
JSON_Value *array_value;
JSON_Array *array;

G_gisinit(argv[0]);

Expand Down Expand Up @@ -102,6 +106,9 @@ int main(int argc, char *argv[])
_("If units are not specified, current project units are used. "
"Meters are used by default in geographic (latlon) projects.");

parm.format = G_define_standard_option(G_OPT_F_FORMAT);
parm.units->guisection = _("Print");

if (G_parser(argc, argv))
exit(EXIT_FAILURE);

Expand Down Expand Up @@ -147,6 +154,15 @@ int main(int argc, char *argv[])
res = (window.ew_res + window.ns_res) / 2;
}

if (strcmp(parm.format->answer, "json") == 0) {
format = JSON;
array_value = json_value_init_array();
array = json_array(array_value);
}
else {
format = PLAIN;
}

G_message(_("Using resolution: %g [%s]"), res / factor, unit);

G_begin_distance_calculations();
Expand Down Expand Up @@ -177,17 +193,19 @@ int main(int argc, char *argv[])
data_type = Rast_get_map_type(fd);
/* Done with file */

/* Show message giving output format */
G_message(_("Output columns:"));
if (coords == 1)
sprintf(formatbuff,
_("Easting, Northing, Along track dist. [%s], Elevation"),
unit);
else
sprintf(formatbuff, _("Along track dist. [%s], Elevation"), unit);
if (clr)
strcat(formatbuff, _(" RGB color"));
G_message("%s", formatbuff);
if (format == PLAIN) {
/* Show message giving output format */
G_message(_("Output columns:"));
if (coords == 1)
sprintf(formatbuff,
_("Easting, Northing, Along track dist. [%s], Elevation"),
unit);
else
sprintf(formatbuff, _("Along track dist. [%s], Elevation"), unit);
if (clr)
strcat(formatbuff, _(" RGB color"));
G_message("%s", formatbuff);
}

/* Get Profile Start Coords */
if (parm.coord_file->answer) {
Expand All @@ -207,7 +225,7 @@ int main(int argc, char *argv[])

if (havefirst)
do_profile(e1, e2, n1, n2, coords, res, fd, data_type, fp,
null_string, unit, factor);
null_string, unit, factor, format, name, array);
e1 = e2;
n1 = n2;
havefirst = TRUE;
Expand All @@ -232,7 +250,7 @@ int main(int argc, char *argv[])

/* Get profile info */
do_profile(e1, e2, n1, n2, coords, res, fd, data_type, fp,
null_string, unit, factor);
null_string, unit, factor, format, name, array);
}
else {
for (i = 0; i <= k - 2; i += 2) {
Expand All @@ -246,11 +264,21 @@ int main(int argc, char *argv[])

/* Get profile info */
do_profile(e1, e2, n1, n2, coords, res, fd, data_type, fp,
null_string, unit, factor);
null_string, unit, factor, format, name, array);
}
}
}

if (format == JSON) {
char *serialized_string = json_serialize_to_string_pretty(array_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(array_value);
}

Rast_close(fd);
fclose(fp);

Expand All @@ -264,7 +292,8 @@ int main(int argc, char *argv[])
/* Establish parameters */
int do_profile(double e1, double e2, double n1, double n2, int coords,
double res, int fd, int data_type, FILE *fp, char *null_string,
const char *unit, double factor)
const char *unit, double factor, enum OutputFormat format,
char *name, JSON_Array *array)
{
double rows, cols, LEN;
double Y, X, k;
Expand All @@ -284,7 +313,8 @@ int do_profile(double e1, double e2, double n1, double n2, int coords,
/* Special case for no movement */
e = e1;
n = n1;
read_rast(e, n, dist / factor, fd, coords, data_type, fp, null_string);
read_rast(e, n, dist / factor, fd, coords, data_type, fp, null_string,
format, name, array);
}

k = res / hypot(rows, cols);
Expand All @@ -303,7 +333,7 @@ int do_profile(double e1, double e2, double n1, double n2, int coords,
/* SE Quad or due east */
for (e = e1, n = n1; e < e2 || n > n2; e += X, n -= Y) {
read_rast(e, n, dist / factor, fd, coords, data_type, fp,
null_string);
null_string, format, name, array);
/* d+=res; */
dist += G_distance(e - X, n + Y, e, n);
}
Expand All @@ -313,7 +343,7 @@ int do_profile(double e1, double e2, double n1, double n2, int coords,
/* NE Quad or due north */
for (e = e1, n = n1; e < e2 || n < n2; e += X, n += Y) {
read_rast(e, n, dist / factor, fd, coords, data_type, fp,
null_string);
null_string, format, name, array);
/* d+=res; */
dist += G_distance(e - X, n - Y, e, n);
}
Expand All @@ -323,7 +353,7 @@ int do_profile(double e1, double e2, double n1, double n2, int coords,
/* SW Quad or due south */
for (e = e1, n = n1; e > e2 || n > n2; e -= X, n -= Y) {
read_rast(e, n, dist / factor, fd, coords, data_type, fp,
null_string);
null_string, format, name, array);
/* d+=res; */
dist += G_distance(e + X, n + Y, e, n);
}
Expand All @@ -333,7 +363,7 @@ int do_profile(double e1, double e2, double n1, double n2, int coords,
/* NW Quad or due west */
for (e = e1, n = n1; e > e2 || n < n2; e -= X, n += Y) {
read_rast(e, n, dist / factor, fd, coords, data_type, fp,
null_string);
null_string, format, name, array);
/* d+=res; */
dist += G_distance(e + X, n - Y, e, n);
}
Expand Down
103 changes: 103 additions & 0 deletions raster/r.profile/r.profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ <h2>OUTPUT FORMAT</h2>
If the units are not specified, current coordinate reference system's units will be used.
In case of geographic CRS (latitude/longitude), meters are used as default unit.

Finally, the output from <em>r.info</em> can be output in JSON by passing the <b>format=json</b> option.

<h2>NOTES</h2>

The profile resolution is measured exactly from the supplied end or
Expand Down Expand Up @@ -151,6 +153,107 @@ <h3>Extraction of values along profile defined by coordinates (variant 2)</h3>
4054.027749 73.988029
</pre></div>

<h3>JSON Output</h3>
<div class="code"><pre>
r.profile -g input=elevation coordinates=641712,226095,641546,224138,641546,222048,641049,221186 -c format=json resolution=1000
</pre></div>

The output looks as follows:

<div class="code"><pre>
[
{
"easting": 641712,
"northing": 226095,
"distance": 0,
"elevation": 84.661506652832031,
"red": 113,
"green": 255,
"blue": 0
},
{
"easting": 641627.47980925441,
"northing": 225098.57823319823,
"distance": 1000.0000000000125,
"elevation": 98.179061889648438,
"red": 255,
"green": 241,
"blue": 0
},
{
"easting": 641546,
"northing": 224138,
"distance": 1964.0277492948007,
"elevation": 83.638137817382812,
"red": 100,
"green": 255,
"blue": 0
},
{
"easting": 641546,
"northing": 223138,
"distance": 2964.0277492948007,
"elevation": 89.141029357910156,
"red": 169,
"green": 255,
"blue": 0
},
{
"easting": 641546,
"northing": 222138,
"distance": 3964.0277492948007,
"elevation": 78.497756958007812,
"red": 35,
"green": 255,
"blue": 0
},
{
"easting": 641546,
"northing": 222048,
"distance": 4054.0277492948007,
"elevation": 73.988029479980469,
"red": 0,
"green": 249,
"blue": 17
}
]
</pre></div>

<h3>Using JSON output with Python for plotting data</h3>

The JSON output makes for ease of integration with popular python data science libraries. For instance, here
is an example of creating a scatterplot of distance vs elevation with color coding.

<div class="code"><pre>
import grass.script as gs
import pandas as pd
import matplotlib.pyplot as plt

# Run r.profile command
elevation = gs.read_command(
"r.profile",
input="elevation",
coordinates="641712,226095,641546,224138,641546,222048,641049,221186",
format="json",
flags="gc"
)

# Load the JSON data into a dataframe
df = pd.read_json(elevation)

# Convert the RGB color values to hex format for matplotlib
df["color"] = df.apply(lambda x: "#{:02x}{:02x}{:02x}".format(int(x["red"]), int(x["green"]), int(x["blue"])), axis=1)

# Create the scatter plot
plt.figure(figsize=(10, 6))
plt.scatter(df['distance'], df['elevation'], c=df['color'], marker='o')
plt.title('Profile of Distance vs. Elevation with Color Coding')
plt.xlabel('Distance (meters)')
plt.ylabel('Elevation')
plt.grid(True)
plt.show()
</pre></div>

<h2>SEE ALSO</h2>

<em>
Expand Down
Loading

0 comments on commit 7f4aabf

Please sign in to comment.