Skip to content

Commit 23e2c7f

Browse files
r.proj: Add JSON support (#5549)
1 parent 7c7162b commit 23e2c7f

File tree

6 files changed

+309
-24
lines changed

6 files changed

+309
-24
lines changed

raster/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ build_program_in_subdir(
420420
grass_gis
421421
grass_raster
422422
grass_gproj
423+
grass_parson
423424
${LIBM}
424425
OPTIONAL_DEPENDS
425426
OPENMP)

raster/r.proj/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ MODULE_TOPDIR = ../..
22

33
PGM = r.proj
44

5-
LIBES = $(GPROJLIB) $(RASTERLIB) $(GISLIB) $(MATHLIB)
5+
LIBES = $(GPROJLIB) $(RASTERLIB) $(GISLIB) $(MATHLIB) $(PARSONLIB)
66
DEPENDENCIES = $(GPROJDEP) $(RASTERDEP) $(GISDEP)
77

88
EXTRA_LIBS = $(OPENMP_LIBPATH) $(OPENMP_LIB)

raster/r.proj/main.c

Lines changed: 139 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,12 @@
5858
#include <stdlib.h>
5959
#include <string.h>
6060
#include <unistd.h>
61+
#include <math.h>
6162
#include <grass/gis.h>
6263
#include <grass/raster.h>
6364
#include <grass/gprojects.h>
6465
#include <grass/glocale.h>
66+
#include <grass/parson.h>
6567
#include "r.proj.h"
6668

6769
/* modify this table to add new methods */
@@ -132,14 +134,17 @@ int main(int argc, char **argv)
132134
*indbase, /* name of input database */
133135
*interpol, /* interpolation method */
134136
*memory, /* amount of memory for cache */
135-
*res; /* resolution of target map */
137+
*res, /* resolution of target map */
138+
*format; /* output format */
136139

137140
#ifdef HAVE_PROJ_H
138141
struct Option *pipeline; /* name of custom PROJ pipeline */
139142
#endif
140143
struct Cell_head incellhd, /* cell header of input map */
141144
outcellhd; /* and output map */
142145

146+
enum OutputFormat outputFormat;
147+
143148
G_gisinit(argv[0]);
144149

145150
module = G_define_module();
@@ -195,6 +200,13 @@ int main(int argc, char **argv)
195200
res->description = _("Resolution of output raster map");
196201
res->guisection = _("Target");
197202

203+
format = G_define_standard_option(G_OPT_F_FORMAT);
204+
format->options = "plain,shell,json";
205+
format->descriptions = _("plain;Human readable text output;"
206+
"shell;shell script style text output;"
207+
"json;JSON (JavaScript Object Notation);");
208+
format->guisection = _("Print");
209+
198210
#ifdef HAVE_PROJ_H
199211
pipeline = G_define_option();
200212
pipeline->key = "pipeline";
@@ -222,8 +234,10 @@ int main(int argc, char **argv)
222234

223235
gprint_bounds = G_define_flag();
224236
gprint_bounds->key = 'g';
225-
gprint_bounds->description = _("Print input map's bounds in the current "
226-
"projection and exit (shell style)");
237+
gprint_bounds->description =
238+
_("[DEPRECATED] Print input map's bounds in the current "
239+
"projection and exit (shell style). This flag is obsolete and will "
240+
"be removed in a future release. Use format=shell instead.");
227241
gprint_bounds->guisection = _("Print");
228242

229243
/* The parser checks if the map already exists in current mapset,
@@ -234,6 +248,28 @@ int main(int argc, char **argv)
234248
if (G_parser(argc, argv))
235249
exit(EXIT_FAILURE);
236250

251+
if (strcmp(format->answer, "json") == 0) {
252+
outputFormat = JSON;
253+
}
254+
else if (strcmp(format->answer, "shell") == 0) {
255+
outputFormat = SHELL;
256+
}
257+
else {
258+
outputFormat = PLAIN;
259+
}
260+
261+
if (outputFormat != PLAIN && !print_bounds->answer && !list->answer) {
262+
G_fatal_error(
263+
_("The format option can only be used with -%c or -%c flags"),
264+
print_bounds->key, list->key);
265+
}
266+
267+
if (gprint_bounds->answer) {
268+
G_warning(_("Flag 'g' is deprecated and will be removed in a future "
269+
"release. Please use format=shell instead."));
270+
outputFormat = SHELL;
271+
}
272+
237273
/* get the method */
238274
for (method = 0; (ipolname = menu[method].name); method++)
239275
if (strcmp(ipolname, interpol->answer) == 0)
@@ -246,7 +282,7 @@ int main(int argc, char **argv)
246282

247283
mapname = outmap->answer ? outmap->answer : inmap->answer;
248284
if (mapname && !list->answer && !overwrite && !print_bounds->answer &&
249-
!gprint_bounds->answer && G_find_raster(mapname, G_mapset()))
285+
outputFormat != SHELL && G_find_raster(mapname, G_mapset()))
250286
G_fatal_error(_("option <%s>: <%s> exists. To overwrite, use the "
251287
"--overwrite flag"),
252288
"output", mapname);
@@ -261,8 +297,8 @@ int main(int argc, char **argv)
261297
#endif
262298
G_get_window(&outcellhd);
263299

264-
if (gprint_bounds->answer && !print_bounds->answer)
265-
print_bounds->answer = gprint_bounds->answer;
300+
if (outputFormat == SHELL && !print_bounds->answer)
301+
print_bounds->answer = 1;
266302
curr_proj = G_projection();
267303

268304
/* Get projection info for output mapset */
@@ -297,15 +333,47 @@ int main(int argc, char **argv)
297333
if (list->answer) {
298334
int i;
299335
char **srclist;
336+
JSON_Array *maps_array = NULL;
337+
JSON_Value *maps_value = NULL;
338+
339+
if (outputFormat == JSON) {
340+
maps_value = json_value_init_array();
341+
if (maps_value == NULL) {
342+
G_fatal_error(
343+
_("Failed to initialize JSON array. Out of memory?"));
344+
}
345+
maps_array = json_array(maps_value);
346+
}
300347

301348
G_verbose_message(_("Checking project <%s> mapset <%s>"),
302349
inlocation->answer, setname);
303350
srclist = G_list(G_ELEMENT_RASTER, G_getenv_nofatal("GISDBASE"),
304351
G_getenv_nofatal("LOCATION_NAME"), setname);
305352
for (i = 0; srclist[i]; i++) {
306-
fprintf(stdout, "%s\n", srclist[i]);
353+
switch (outputFormat) {
354+
case SHELL:
355+
case PLAIN:
356+
fprintf(stdout, "%s\n", srclist[i]);
357+
break;
358+
359+
case JSON:
360+
json_array_append_string(maps_array, srclist[i]);
361+
break;
362+
}
363+
}
364+
if (outputFormat == JSON) {
365+
char *serialized_string = NULL;
366+
serialized_string = json_serialize_to_string_pretty(maps_value);
367+
if (serialized_string == NULL) {
368+
G_fatal_error(_("Failed to initialize pretty JSON string."));
369+
}
370+
puts(serialized_string);
371+
json_free_serialized_string(serialized_string);
372+
json_value_free(maps_value);
373+
}
374+
else {
375+
fflush(stdout);
307376
}
308-
fflush(stdout);
309377
exit(EXIT_SUCCESS); /* leave r.proj after listing */
310378
}
311379

@@ -378,6 +446,9 @@ int main(int argc, char **argv)
378446
ocols = outcellhd.cols;
379447

380448
if (print_bounds->answer) {
449+
JSON_Value *root_value = NULL;
450+
JSON_Object *root_object = NULL;
451+
381452
G_message(_("Input map <%s@%s> in project <%s>:"), inmap->answer,
382453
setname, inlocation->answer);
383454

@@ -407,17 +478,73 @@ int main(int argc, char **argv)
407478
G_format_easting(ieast, east_str, curr_proj);
408479
G_format_easting(iwest, west_str, curr_proj);
409480

410-
if (gprint_bounds->answer) {
411-
fprintf(stdout, "n=%s s=%s w=%s e=%s rows=%d cols=%d\n", north_str,
412-
south_str, west_str, east_str, irows, icols);
481+
if (outputFormat == JSON) {
482+
root_value = json_value_init_object();
483+
if (root_value == NULL) {
484+
G_fatal_error(
485+
_("Failed to initialize JSON object. Out of memory?"));
486+
}
487+
root_object = json_object(root_value);
413488
}
414-
else {
489+
490+
switch (outputFormat) {
491+
case PLAIN:
415492
fprintf(stdout, "Source cols: %d\n", icols);
416493
fprintf(stdout, "Source rows: %d\n", irows);
417494
fprintf(stdout, "Local north: %s\n", north_str);
418495
fprintf(stdout, "Local south: %s\n", south_str);
419496
fprintf(stdout, "Local west: %s\n", west_str);
420497
fprintf(stdout, "Local east: %s\n", east_str);
498+
break;
499+
500+
case SHELL:
501+
fprintf(stdout, "n=%s s=%s w=%s e=%s rows=%d cols=%d\n", north_str,
502+
south_str, west_str, east_str, irows, icols);
503+
break;
504+
505+
case JSON:
506+
if (isfinite(inorth)) {
507+
json_object_set_number(root_object, "north", inorth);
508+
}
509+
else {
510+
json_object_set_null(root_object, "north");
511+
}
512+
513+
if (isfinite(isouth)) {
514+
json_object_set_number(root_object, "south", isouth);
515+
}
516+
else {
517+
json_object_set_null(root_object, "south");
518+
}
519+
520+
if (isfinite(iwest)) {
521+
json_object_set_number(root_object, "west", iwest);
522+
}
523+
else {
524+
json_object_set_null(root_object, "west");
525+
}
526+
527+
if (isfinite(ieast)) {
528+
json_object_set_number(root_object, "east", ieast);
529+
}
530+
else {
531+
json_object_set_null(root_object, "east");
532+
}
533+
534+
json_object_set_number(root_object, "rows", irows);
535+
json_object_set_number(root_object, "cols", icols);
536+
break;
537+
}
538+
539+
if (outputFormat == JSON) {
540+
char *serialized_string = NULL;
541+
serialized_string = json_serialize_to_string_pretty(root_value);
542+
if (serialized_string == NULL) {
543+
G_fatal_error(_("Failed to initialize pretty JSON string."));
544+
}
545+
puts(serialized_string);
546+
json_free_serialized_string(serialized_string);
547+
json_value_free(root_value);
421548
}
422549

423550
exit(EXIT_SUCCESS);

raster/r.proj/r.proj.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ struct menu {
3333
char *text; /* menu display - full description */
3434
};
3535

36+
enum OutputFormat { PLAIN, SHELL, JSON };
37+
3638
extern void bordwalk(const struct Cell_head *, struct Cell_head *,
3739
const struct pj_info *, const struct pj_info *,
3840
const struct pj_info *, int);

raster/r.proj/r.proj.md

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ and resolution of the target project should be set appropriately
152152
beforehand.
153153

154154
A simple way to do this is to check the projected bounds of the input
155-
map in the current project's CRS using the **-p** flag. The **-g** flag
156-
reports the same thing, but in a form which can be directly cut and
157-
pasted into a *[g.region](g.region.md)* command. After setting the
158-
region in that way you might check the cell resolution with "*g.region
155+
map in the current project's CRS using the **-p** flag. The **format=shell**
156+
option with **-p** flag reports the same thing, but in a form which can be
157+
directly cut and pasted into a *[g.region](g.region.md)* command. After setting
158+
the region in that way you might check the cell resolution with "*g.region
159159
-p*" then snap it to a regular grid with *[g.region](g.region.md)*'s
160160
**-a** flag. E.g. `g.region -a res=5 -p`. Note that this is just a rough
161161
guide.
@@ -179,12 +179,22 @@ be odd with trimming.
179179

180180
## EXAMPLES
181181

182+
To list raster maps in input mapset:
183+
184+
```sh
185+
# list raster maps in plain output format
186+
r.proj project=ll_wgs84 mapset=user1 -l
187+
188+
# list raster maps in JSON output format
189+
r.proj project=ll_wgs84 mapset=user1 -l format=json
190+
```
191+
182192
### Inline method
183193

184-
With GRASS running in the destination project use the **-g** flag to
185-
show the input map's bounds once reprojected into the current working
186-
CRS, then use that to set the region bounds before performing the
187-
reprojection:
194+
With GRASS running in the destination project use the **format=shell** option
195+
with **-p** flag to show the input map's bounds once reprojected into the
196+
current working CRS, then use that to set the region bounds before performing
197+
the reprojection:
188198

189199
```sh
190200
# calculate where output map will be
@@ -197,9 +207,20 @@ Local west: 14271663.19157564
197207
Local east: 14409956.2693866
198208

199209
# same calculation, but in a form which can be cut and pasted into a g.region call
200-
r.proj input=elevation project=ll_wgs84 mapset=user1 -g
210+
r.proj input=elevation project=ll_wgs84 mapset=user1 -p format=shell
201211
n=-4265502.30382993 s=-4473453.15255565 w=14271663.19157564 e=14409956.2693866 rows=12277 cols=8162
202212

213+
# calculate where output map will be in JSON format
214+
r.proj input=elevation project=ll_wgs84 mapset=user1 -p format=json
215+
{
216+
"north": -4265502.30382993,
217+
"south": -4473453.15255565,
218+
"west": 14271663.19157564,
219+
"east": 14409956.2693866,
220+
"rows": 12277,
221+
"cols": 8162
222+
}
223+
203224
g.region n=-4265502.30382993 s=-4473453.15255565 \
204225
w=14271663.19157564 e=14409956.2693866 rows=12277 cols=8162 -p
205226
projection: 99 (Mercator)
@@ -262,6 +283,38 @@ r.proj input=elevation.dem output=elevation.dem.reproj \
262283
project=source_project_name mapset=PERMANENT res=5 method=bicubic
263284
```
264285

286+
### Using r.proj JSON output with python
287+
288+
Displaying the input map's bounds in the current projection in JSON format
289+
using python:
290+
291+
```python
292+
import grass.script as gs
293+
294+
# Run the r.proj command to print the input map's bounds in the current
295+
# projection using JSON output format
296+
bounds = gs.parse_command(
297+
"r.proj",
298+
project="nc_spm_full_v2alpha2",
299+
mapset="PERMANENT",
300+
input="elevation",
301+
flags="p",
302+
format="json",
303+
)
304+
305+
for bound, value in bounds.items():
306+
print(f"{bound}: {value}")
307+
```
308+
309+
```sh
310+
north: 228500
311+
south: 215000
312+
west: 630000
313+
east: 645000
314+
rows: 1350
315+
cols: 1500
316+
```
317+
265318
## REFERENCES
266319

267320
1. Evenden, G.I. (1990) [Cartographic projection procedures for the

0 commit comments

Comments
 (0)