From 38925348778a9194684c6f0316ec5811a9025933 Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Fri, 27 Sep 2024 16:13:42 -0400 Subject: [PATCH 1/2] Add tutorial comparing R and Python APIs --- .../execute-results/html.json | 14 + .../images/R_Python_compare.png | Bin 0 -> 30715 bytes ...omparison_r_vs_python_grass_interfaces.qmd | 289 ++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 _freeze/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces/execute-results/html.json create mode 100644 content/tutorials/r_python_interfaces_comparison/images/R_Python_compare.png create mode 100644 content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces.qmd diff --git a/_freeze/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces/execute-results/html.json b/_freeze/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces/execute-results/html.json new file mode 100644 index 0000000..085866a --- /dev/null +++ b/_freeze/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces/execute-results/html.json @@ -0,0 +1,14 @@ +{ + "hash": "02b5f6931312f20d479fb4fbc02fb26f", + "result": { + "markdown": "---\ntitle: \"Quick comparison: R and Python GRASS interfaces\"\nauthor: \"Veronica Andreo\"\ndate: 2024-04-01\ndate-modified: today\nformat:\n html:\n toc: true\n code-tools: true\n code-copy: true\n code-fold: false\ncategories: [GRASS GIS, Python, R, Intermediate]\nengine: knitr\nexecute:\n eval: false\n---\n\n\n![](images/R_Python_compare.png){.preview-image width=50%}\n\nIn this short tutorial we will highlight the similarities of R and Python GRASS interfaces\nin order to streamline the use of GRASS GIS within R and Python communities.\nAs you may know, there's\nan R package called [rgrass](https://github.com/rsbivand/rgrass/) that provides\nbasic functionality to read and write data from and into GRASS database as well\nas to execute GRASS tools in either existing or temporary GRASS projects.\nThe [GRASS Python API](https://grass.osgeo.org/grass-stable/manuals/libpython/index.html),\non the other hand, is composed of various packages that provide classes and\nfunctions for low and high level tasks, including those that can be executed\nwith rgrass.\n\n\n\nThere are some parallelisms between the\n**rgrass** and **grass.script**/**grass.jupyter** packages, i.e.,\nR and Python interfaces to GRASS GIS.\nLet's review them and go through some examples.\n\n\n| Task | rgrass function | GRASS Python API function |\n|------------------------------------------------------------|--------------------------------|---------------------------------------------------------|\n| Load library | library(rgrass) | import grass.script as gs
import grass.jupyter as gj |\n| Start GRASS and set all needed
environmental variables | initGRASS() | gs.setup.init() for scripts,
gj.init() for notebooks |\n| Execute GRASS commands | execGRASS() | gs.run_command(),
gs.read_command(),
gs.parse_command() |\n| Read raster and vector data
from GRASS | read_RAST(),
read_VECT() | gs.array.array(),
n/a |\n| Write raster and vector data
into GRASS | write_RAST(),
write_VECT() | gs.array.write(),
n/a |\n| Get raster and vector info | n/a,
vInfo() | gs.raster_info(),
gs.vector_info() |\n| Close GRASS session | unlink_.gislock() | gs.setup.finish(),
gj.finish() |\n\n: R and Python GRASS interfaces compared {.striped .hover}\n\n## Comparison examples\n\nLet's see how usage examples would look like.\n\n1. **Load the library**: We need to\nload the libraries that allow us to interface with GRASS GIS\nfunctionality and (optionally) data. For the Python case, we first need to add\nthe GRASS python package path to our system's path.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(rgrass)\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\nimport sys\nimport subprocess\n\nsys.path.append(\n subprocess.check_output([\"grass\", \"--config\", \"python_path\"], text=True).strip()\n)\n\nimport grass.script as gs\nimport grass.jupyter as gj\n```\n:::\n\n\n:::\n\n2. **Start a GRASS session**: Once we loaded or imported the packages, we\nstart a GRASS session. We need to pass the path to a\ntemporary or existing GRASS project.\nIn the case of R, `initGRASS` will automatically look for GRASS binaries, alternatively we can\nspecify the path to the binaries ourselves.\nIn the case of Python, it is worth noting that while grass.script and grass.jupyter init functions\ntake the same arguments, `gj.init` also sets other environmental variables to\nstreamline work within Jupyter Notebooks, e.g., overwrite is set to true so cells\ncan be executed multiple times.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsession <- initGRASS(gisBase = \"/usr/lib/grass84\", # where grass binaries live, `grass --config path`\n gisDbase = \"/home/user/grassdata\", # path to grass database or folder where your project lives\n location = \"nc_basic_spm_grass7\", # existing project name\n mapset = \"PERMANENT\" # mapset name\n )\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# With grass.script for scripts\nsession = gs.setup.init(path=\"/home/user/grassdata\",\n location=\"nc_basic_spm_grass7\",\n mapset=\"PERMANENT\")\n# Optionally, the path to a mapset\nsession = gs.setup.init(\"/home/user/grassdata/nc_basic_spm_grass7/PERMANENT\")\n\n# With grass.jupyter for notebooks\nsession = gj.init(path=\"/home/user/grassdata\",\n location=\"nc_basic_spm_grass7\",\n mapset=\"PERMANENT\")\n# Optionally, the path to a mapset\nsession = gj.init(\"~/grassdata/nc_basic_spm_grass7/PERMANENT\")\n```\n:::\n\n\n:::\n\n3. **Execute GRASS commands**: Both interfaces work pretty similarly, the\nfirst argument is always the GRASS tool name and then we pass the parameters\nand flags. While in R we basically use `execGRASS()` for all GRASS commands, in\nthe Python API, we have different wrappers to execute GRASS commands depending\non the nature of their output.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Map output\nexecGRASS(\"r.slope.aspect\",\n elevation = \"elevation\",\n slope = \"slope\",\n aspect = \"aspect\")\n\n# Text output\nexecGRASS(\"g.region\",\n raster = \"elevation\",\n flags = \"p\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Map output\ngs.run_command(\"r.slope.aspect\",\n elevation=\"elevation\",\n slope=\"slope\",\n aspect=\"aspect\")\n# Text output\nprint(gs.read_command(\"g.region\",\n raster=\"elevation\",\n flags=\"p\"))\n# Text output - dictionary\nregion = gs.parse_command(\"g.region\",\n raster=\"elevation\",\n flags=\"g\")\nregion\n```\n:::\n\n\n:::\n\n4. **Read raster and vector data into other R or Python formats**:\n*rgrass* functions `read_RAST()` and `read_VECT()` convert GRASS raster and\nvector maps into terra's SpatRaster and SpatVector objects within R.\nIn the case of Python, GRASS\nraster maps that can be converted into numpy arrays through\n`gs.array.array()`. Vector attribute data can be converted into\nPandas data frames in various ways.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Raster\nelevr <- read_RAST(\"elevation\")\n\n# Vector\nschoolsr <- read_VECT(\"schools\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Raster into numpy array\nelev = gs.array.array(\"elevation\")\n\n# Vector attributes\nimport pandas as pd\nschools = gs.parse_command(\"v.db.select\", map=\"schools\", format=\"json\")\npd.DataFrame(schools[\"records\"])\n\n# Vector geometry and attributes to GeoJSON\ngs.run_command(\"v.out.ogr\", input=\"schools\", output=\"schools.geojson\", format=\"GeoJSON\")\n```\n:::\n\n\n:::\n\n\n5. **Write R or Python objects into GRASS raster and vector maps**: R terra's\nSpatRaster and SpatVector objects can be written (back) into GRASS format with\n`write_RAST()` and `write_VECT()` functions. Within the Python environment,\nnumpy arrays can also be written (back) into GRASS raster maps with the\n`write()` method.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Raster\nwrite_RAST(elevr, \"elevation_r\")\n\n# Vector\nwrite_VECT(schoolsr, \"schools_r\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Raster\nelev.write(mapname=\"elev_np\", overwrite=True)\n\n# GeoJSON into GRASS vector\ngs.run_command(\"v.in.ogr\", input=\"schools.geojson\", output=\"schools2\")\n```\n:::\n\n\n:::\n\n\n6. **Close GRASS GIS session**: In general, just closing R or Rstudio, as well\nas shutting down Jupyter notebook, will clean up and close the GRASS session\nproperly. Sometimes, however, especially if the user changed mapset within the\nworkflow, it is better to clean up explicitly before closing.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nunlink_.gislock()\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\nsession.finish()\n```\n:::\n\n\n:::\n\n## Final remarks\n\nThe examples and comparisons presented here are intended to facilitate the\ncombination of tools and languages as well as the exchange of data and format\nconversions. We hope that's useful as a starting point for the implementation\nof different use cases and workflows that suit the needs of users.\nSee R and Python tutorials for more examples:\n\n* [GRASS and Python tutorial for beginners](../get_started/fast_track_grass_and_python.qmd)\n* [GRASS and R tutorial for beginners](../get_started/fast_track_grass_and_R.qmd)\n\n## References\n\n* [GRASS Python API docs](https://grass.osgeo.org/grass-stable/manuals/libpython/index.html)\n* [rgrass docs](https://rsbivand.github.io/rgrass/)\n\n\n***\n\n:::{.smaller}\nThe development of this tutorial was funded by the US\n[National Science Foundation (NSF)](https://www.nsf.gov/),\naward [2303651](https://www.nsf.gov/awardsearch/showAward?AWD_ID=2303651).\n:::\n", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": {}, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/content/tutorials/r_python_interfaces_comparison/images/R_Python_compare.png b/content/tutorials/r_python_interfaces_comparison/images/R_Python_compare.png new file mode 100644 index 0000000000000000000000000000000000000000..438104c32fa44b1d8fe6e396766492a16110c2b7 GIT binary patch literal 30715 zcmZ6ybx_;E_CB110KpSzaZ4yx+)Hs99E!BX-Cc?ncPUQM;ssh-ptxI*0>x=@x8e@P z<(J-jzwaOK&Sa9wOg=gL>^aXq=j_=iwU=@PxRkg60DwS29;pEUfG_|6AP|Cu{^rlk z=MMB6jHv4{jOhhO6pR}q@HN<)CjjG;6F zkpqVmn_^PQey}H1A))5lATTj>Fd+;;+|B$6qNt4lhQJdnEU$Bx=vo(Qo4z%BXErXW z@*21wv~FhU8MwDEwbjD-y8b%~4iUxfAlmhZ;ycB9j9_;#KZfKQ;v+lv9P{5enJ%a1^PF)8i2Cl&c3 z%{hqD$K7;J)}(Y?_hXj=0t$-^ynRc$TmK$g?~IDE7}E2-yeE`;I-QDHXGJr2e(Xxu zC?OLnna}=^=6dibjQ-7#RszF1sLZTq0AL6|8lZ1MgnAwS-nbu;5NUOH_(|2p@aVtj zoDs*guaBA7jPX+g+i;G^bc|I2Dm;kuPXQW1Trkp&jVgP{{pp?amf>8eE;q9 zStr@&diTc*EVPNs|AA93#8lcxZeX@UkGeaC0YFR8LnuiOBb!Xo30g(k8^v>giDMtc z(+?4Zj^3+Ua(9@t79G(*A|j%AtgeOu5u!YPRUmgzkV*~7`38T$Pb7P-6hAz#XXHEc zqb2ujP{_k0$gSmy3<+(JoWhYQ3cc(FKuG|Nx6P(W@G&2hE>F2yD>$;&{Q@(j^rSXN zHS}`lQwuGhxEU~VSnsT)&f+K%^S?muP)8WFEbk)nN+_neco10QMi+mGT@;Q_%M@eE zCkJ{|F&ez@0;MEQ$*5%`{_9EZ??YxqZipzA(?IFC&d?ypsa@DAE^%dzM)qYGuR)CV zYI~=24ae0`;pU3^|4+IuE;3=_6RgVyD&jt>edVlkWN~T1afU1@-YTCQa&P zcg(ht$l>s|tVO(N3;BQio&+j6p41kW;rWwlfT8mwiu~Ym7C+#fCuiuO+VtA46?rF@ z_LF5Y>>SVDjH8N9Bfft@q|R*JTI$NnEj zi2ltqqYI1ppY!ejuxmZf=~`f|-GeXqQoRwTwT3DO`tqFjK3`wkw*P3)xfB#VIV$Hq z{}+rH_>?gP`_h1XX-Q7t5s-X<{FX4#A5e$ArUdw?1UDV3=WDwQmT6S%79rb%e~5d? z$Z;ES0#XCxMQ;cH#kL&H@~DzX8t>5?fU^Pf@)PME1T6Al!F+m0V#v`>X~18P%iCIB zNJtfY8i3KF(q`e;;>(hMnd&XG8sakjONm(mKm_=2aEwzIlxx?hyn22|3V5#&y3oBk z7!e@)R<@EHJtIO#8^Zv;z{Bz5fr!pECI5kFBEap%7oqzRaZ$gfz=-zD|2XQTeAF3B zHyOwVD=rcN1{imot>lVc{~jYj-TKVrEOYpxgAGO}vzqxel{^<{JW}p2S~|3)P4_<@ ztHoMlbBRh%4-kB~g%SP0!?-sd7|D`~DS9XO_A5i1FOy{UuXyS1bI9bSeRRCn@xD~q zF~u_)TEPF-SQv6EL;oB`e00)8FqD=`ygS2shCV|*p_jxkF6({i6DVIQY{XCG|o zStgV?_jm{+z*%l3gV2xse;82480;YjH4&CaMR=cy4p{}}3nZv+*dg(MS|&mx|H$^Q zN9ddOsIHUtNV5@ViBr@5?{~yzaMx?@^g zdg$Nq49Zprd4n*0^ooNCfz;y?Gfoj9LpEz`fvQAo_eelL~nzulruN@|Ms16VjvON z6bd(~<{=wi`Z8ZJ=$6o5MExPM=ofF~^kbuF;~pV?g|o{h64T1cu}X2Q|1y+qB)!Uz zOH@Ks94E7-kB<+)`F`7{2L#QId4iE{m&f*p#ZB`~JtGOM|m+n#^OPic(Omgq{@;|T2^#FUFV93kPKcn*wTADx*I)`nC*cHQAa zPV*Ds6Gny?c7(_qbE85~9Zbk9vLNZB9!SEKMMn!E%A4NR8XWT%-w+#I5_rapf6Z@v zA4)+7?C7W8(zzOwqibE7oSb~~fzZe64EHQh_yW+96zS~tJT@S%$#~IWu%Wp~;r87@ zd~cbplM_*n{jBF)j+-qQ7KgD$Uu5taQHPzfrW9U`9JJ_3Loj=9N-v>dAqeiJ3-CRq z->NsGG>r(fVF~dBdtpQUQiB{&(;&3C0;^eGFae}1uCr8#jpchuq=Xv)>x=K1bii+4 z%1lb2E8K1#yI3TG6Wb7qjr&J&%y1fFW3IkZ{3fi^Oy;~etYBP#V_8E z==2}-#l`qyzXhj08HkvKKg-L&{1}<+4M%Q8mfI3*-GU>ZU0sHM7`^nPGQ=Mz+fohi ze#`vn%n&sF7o(YIt1sn(haCliS77Rok`+mIQdNgYE5yRhOy!gbZb9H6R zIZ{4UJ?hz0m@L9{rjnzr>ggW}*0uw~HgUf~2`C5?CD8Vo6g9Nm znf&^lIh_hPG(|>&LyZ*u7nW35^+BUp&}*Xk%ioQzbUOV{Xz>!f?dKa3M|2jvz4-YR z;j5vM2_*EUkD{C=_=>tcaTv6GU6ubzqe96ra8{3hL`&on^V5c^Lq4_>1_1}TQ(CH) zi9tmWaBXRv6-Jgv0sHS`Df}4Y&oCQ^D7WW5uBE7KOhlNr#KTb+(L3Bw3`_FU*7*sN z;@Fj`2F3~MQWQ|2I=Y`dOCjWJe+Y^&GS6d>+=)A0lWR3C++I=NEEb1RZcHB&FvnfG z`)x|op@kZ((t7Nb(WB*`gQ7s>+)nuk`<^SIEl0*)K~!Kxl_4(`)Kshg9Kdbhe7)&< z5a7j%QSIGiZK*exo+9f_OZzvt7@5ujOM@;+bzjZ0PdpW9n@syQ-=lk?5#G&QRv)dsHT?E_a(&n00fY+`5QS9nIgOBk+nG9MkOc+E%s zx*~`Ud74&N)L)%d)dRv|m9-ePZLS?9KrSqH?F#GddeG5p#aim&!8->LR>1CXNpgzv zxdI;eYw0g@qa;ZdZ3Y~$Umnk1#^axhpwF66T`j+}pP8;3gRPCtI8*htb!`@fhb!vt zg&m``axuJ-6{bl0_h7L$cjV}RHuLK2QseT<4@uh7jiFs3>jIT{%MCg(iY$<~PrS6$ zJSZYhd_N?#aLE~f+fguUO}bhx_9*M=Tx8(wx2%m!3*P1r}9{g{`p2(kv@urDYHxL9WO*%n0m;5e5(g-QX-NFJet0UxiLSdDn z>EoN(cz!gkhWMiQgM67^n!4YOcUxhIN-_hdUaD6~Mk<{b)qH(CqxFKmoAIWistOZS zXHHD!cVaWOO+(|#321V%x3fFFpnvB@&JrJKhmJR0KRNweq}nd{X_&=8ssNXM%URav zgR^!VUvcfnpqTl#4i7l0Vm2BNsM-HoNH?SE+iaOb%xgRg{a~nCwK|6O&&5gEoxFwN zb3vKVK)>IQZWT&2sG`Olq*1m+nXG+IE&23KwBsk6&4+CdncgbsH1StdgZP2|k!_yV zFh09(&6%-O12HOxG_~d+;hTZZZ)MKrqQPOeqT0%YHwO&gA>0L|{ie0s+0{Vfn{WW? z2B)VpE>76>w@sFS%j(L2L#?Pcoi3NIXlSvI_46MuqAG<$^ysb4pc<01{fwP5+|u&y z^A^JEUp|Fas##lVCXu*UF(gAiV#7KUZG(*Q`}7lyaA5=ClS>S~BnH#l98s`bfuguZVTo^RiPV_G+XYdxy*{_WCJv*zyG%}Vdz&s5? zP7|>%<^Z8eHxM3PWO8$uDl_6OG#{DSlXCj2E265IT83V_s%He@GeB<>D{LgI;maz* z#Qldx@tDs-@iw^kSJbn;VIV_W5$drK?b0d)2msLFuDZT|{gg1ZT?>l^v!!{1WVA*G zAQAp(q;n%};tX!x`e#yFx4~uK2^W_rmq`iVTUi9L6TRdc`VNejA`ZdBS-YxYEF)o`ELhN zNsLP8%C0+DSZ%l{W3@R#gHq*%DRzr|52cKdG4CJtlB^Ev9pfhTr5_y-AN*u6I}IO2 zGFuHbU4#QMK7Nj+lkkr%3zNGuy)DXKGG6w@>H#X7pQw0@sY4Y*H_~A#kiX9!Gnj6= zXe;`*ex)$7C8v~7hv#{ge)k9S0SZy`xSus!PVcbkgV8ya(o(S{X{;|0&halKXbDq! zE=w}R;2)vJ@;8=SKSFjKyA@x2jn$a=B^zt2pirylW8%iO8&Bg%7ek$)& zo+&(MK7xTkf@=8@P$Of2wj@@3%i1DN3R!gs!6ls+xLH)vNWOokr>B>;@Ij7)=X~eg zUGRZadm&(x*VP1{Ju@) zF&0T@jp0zV)>+;)8W@VSfirR)xvXLI-?9g8F2YumDk_fkz?yhC* zRBXc>rCHixdD134*%R#)uO{-S$GHT`y>GR)pCSa>f4sR94#UXK&Q`%AlW>OxiU94u zRhtC4;$uyCDA(1f+XBN4?7HH3u?sR-6+|kt)w?i3Zq6nWynFUeU2%fHhOHe6NIct2 z^OaR+tP9vfalm9>BWAk5!R~7vJb!apU2(u35b!WT)vZOx_NL`<%O*>+RlKd)?H@cj ztl%`zrF2=;GfLRb&D&sPaj%u0ub*{{b4MY?IE4|#o9yGp!Ap(xkE(dJvIP^~lG)kN)go{;H_?Ij7@Lz+Y3NUSNA55P&7} zcF}P^Y^-%YZq@p?d^J#wjlv->b!OjPm@1*(u62PtG*0BOWwW1x&yrt{>fLM1HLiWp zj0`$U;}44Wh>3Yh7;m(C6t}Feof3~k%6!l>b$tWE??&yITwq9()t3dV|38QWoi zj%Px!odnAZ3e5O?knxJEqXW5@v?K=cgOBGJVJ0q!RK>2y;z^`+l+%lv-6v<4`l4;F zEoxeyjK08Vjcm0U%(2-srcWCyQ|;2Tfg2^qlN2|aPk%R&VyLqFpZF{ri=*_eX8>Q> z&+R4Qtj!A}SdV6>vE#_Afmh-cS%8)0gPi4z48Fsep`E@0gmvp)lpwbw0Qx#Lk^ySM z0%KMUmG$j)$tWMvDAQQ8^W#}%D>=C&W<+Np2Z3V#4u{lze|mSk{e3q(tMq%k7g}2B z=pBDVljuH8bZA{vfwH4n0u<;^{RW*We7%V)8hpFly!YA1W4fy!QB!&HIAV5484tK; zqHhuIhY^@!>=txohET*?eg*XeP6MRufQ2}h+5ilk@pu44Rsuvc=xu9i%mBh3-8==uHtKWwWLj=t;W#!*fyircdDm6MK!wxjgRL_pNy$zydzK zGc3PNqtU2PfyBiH!Qa#IByxHdIOR^P&^y)k<Nmv!--4a8 z-0H-G@)I-OXv~H}g0w*$kAS6h`!D5eK30Wp{vVVM}?=yIA$geqgR0s=z*F!M2goLQk}tepQ;Wcz>PelQNJE zbKfqo2D7eEnqU1}gvfoc2Yq<9lmLf6?c13?V^ss+w;pxicg8cResqpnXq02MZna0G|QK)1g`z*S-#?3f#-*=~(Zkah~9OF6a9~U`5NhJ{dK0-m! zsM9DV!8jYw>qA*ZU&6WhBV8}YF?BM&f4rNiF?u2gJt==*Lu37;-yS<+p<8DEv(H^% zyas80Y)YEG`N-R9YC-M_@SrO3=_>;R6;&FS?hcs%n+qmt*TG9qU<=-IW!C<%2pwgT z?e_I^oNFe?64)6KxS>0Q^=MC#3beK=ahq?pT-jT97cT~kf0jOaZ;IzIm=>5%<7R>c z_nAqmBlsx?qTkS)QZT|QbLtD|lOqRUAa62THgJE4666UBbgwOQETkHEThVfhSKNqJ z@O#wCQUfh4()5)7g*)G&pXmmHcA!$@0GgP0kRxYTRQ%N2Z|#OWHoqF)05&Wi%ns%o zF{ZxnU*o2Shj7zM-jilO9l;{E0L}Z~>{n0GdCrF}gZ$)(-G@c6~C?`UpPtN_V1|*qR&cWhowq(|A^$)M9Zai z*Nz^{39$u(m|~8)en{xh*vZd)zyW6^1FkI-Rw{?yED~e7DQIi(Cu#G8RC{ z&CShzF8lLO@N*2*d7vyYZxS&CQzWSS&D_Trx;5He2x6JIRV zH*Na+c*9O?nb5wuT0z?v)da>)7)n$jdwAGoZy@C7Sv+$+TbKjAa%7U8;AL2VzxU74 z$^s(cUc8{$yt1{>weu9T9fQdoVG#>%|y$Qi&%kU1w=f$Ua#q1eOBJa8U^N=O_u89s=EFr>bN!F)+Np%sF;5D z3X8VsY9p@P_t3|IZQwweF3|Y-_lgasRYl@_4P62RdsO6qRM0mAZq^8o?s9T4>a~2~ zld2I8-JMkPskJe3fQ6CW>xqmYnP}2|DHKPR-%BqLJ zu);CF9%Rta(0qMz^NY$w&Buy_T+VXxsY18AVyHX$lJjb$Q9tGf+1n?Q;}*4i^^Y1P zugmw8e|`Dwc$^Q7Jg2Y~^7SQQW5$o0G9h_Gf)Ou%y+t4NnGg`tWrdNv&_4}J7PL>T zT$MfjQu^)Oy8RlD5TSfmVv(`B3HIvzVyEfnxVIHjPATqt$t?ROzUfD0FYVPv<%c#` zLU=7z`T^jDd7GxY#H${>2m@9#_e$fukf|~y)pG3!ZyYTA^>P6DuS>MFyzt~KW@zT{ z=Y>G*N--bVmqM$gxpCok;19dlFBbd-n}^~jndqiWu`jy5U=VT^E%AP1!qUaUzo)65 zE;z3S>Sma5?G^tV`mVm)^?$tpSfg*5s6{N#A~AYm#S06BBnT?FjDHm}7>S%uA5!h^yJ&d)q5JeNb;0OC1z3Hxi9E39w`z__>P~S z2~BH_X7Sla?%cVc9_+9?N!pggC5dESO)`$~EJnbwgU~#;Yl-4{QDw&xXYRkO<~XFN z2T3m6M%wjE3^9d=K6oA6s2f|;lapa;&KCPNA$1A1!lA``Jx~EjD*iNJ`*cW^dTYpm)yJ|T1HCZq$*6bTS1JF{$pimN`l)6eJ>=rHCFNnPBc1xX4i z#yGn(1MlJYDG%P*24Go17;l+$J^|HZ_Jav{{c1HZFAVt0Cx$|7b)3hn=aQ}C1`;Q7 z5P1=o>3aqPXF+;C&fn+bo0f9w;-3owBE4>hghpOlPKgFOi0ih}1p+6YtbQcPr_FUZ z{tK@r_2f*ZGJ3E7(*}5CM5dg(XqLY8Y_}TV^lgJwv5#9`%&ER6$}=%f?7OMcy<*Hu zs^VgD?F#4P3lw?T+d73Z%IeMiQcdnTfF;$BRu4%whi6Wz)`H8gAX#^VucC=41#HLJ zyp`hHf_{F8ZxhZYH6let_xrQ?RV?J;g6m$xT_$2EJndg?i}OFy=$Ptf`jn2;e}#yl z&NbOjU~6k)Rg5h6%$Gi91MSp&%nb2Ukxq^3mhI#G7a6i}?xF8Dfmupz2SF}AoCT&M z6kg`rzlN@Z{0*k*l&{&@wfosck_H@`JZs1L5i>=+lLZ^G*$5TpSSrt>fSwrEn{gHO zJ@3gR9sr$VYGjpPbDaXO^ColhC$Bqb;y0^n=}=l|c{r*W|6Wy*|H0~R`TpVET2*;j z^kaVT(%k{i!`{}`Wq*_VHkDMmaZ+niQd|A(H~QvW0vks{PLQ*-Mx)^KU+is)k}R;t z%%(Vhl|xOAQ&=p0PoLw6`G?{$>CD*<^9Z3!95%@!WKY3C61w|?Gi+xx=1$W^f08UQ zj8kRaXFlA1tfhmg^YD!R;<%3p;xPTts*~@PsraF_d5F2&?S5+v+sNvPz7+V$x!LF= zKDzhLdkt*KOkn-FFXIgW$wV_gjMp_yq=Bp?!8qIWu^Y9yreD6sb76l;;~C4P=;$<= zE4*(pzIJxq;YQ5P&WZ!MkL%_Ryi26r>>RSN z3k}~ac6^#H;=NmDB{x#WUM-$XWO%T7q($DG+q(4yze(~ZwgU0naX-X%!?CH{ocn$X zqT7JLnVDp$<9b85d{OkaATo-g;qRn~D#n-q0QBL4AmoGFlbdR%mixnU5{YAuKx{>l!{AYM);}Z=?a(LVNR7`nU8nXK>VJy_h_LTy+!F}59=UY@o zyU!$d;jxoBtwiyL#!0#Vf@5V08ykNidmS7~sWjf}?4{AS3jt zzk}rWSQD$jba^Wm$e_?E1-Zt6~OZta-mo42c8}|*VBGo zpT*yj*VgxnTfTf|^2>b&4^IZJ>u;pdgH0@UyT;+g)v1go@w#t;y2vJilTW zb)3X~x*qVJ){54{F;p`S*PSc{%P`6}{h_&B;@dOmG>IAu=0+Q<(+qD^1s2l&6@T63 z4wUjDSiwO#e8%c$6l0rss9bb8W^HddyXF0#rmq7kd@xAw0EG;L?RNXg?yGC_hW#d@ zXTGQv3_!wSvvoO*yuT%Vp3yq#>D^?z?~iW9jH)tw5YRH?y2OY^q0173onrgOtAoyr zCY>pUMr;)^j(H}<)sj=&T`5*VC1kiq2?2^7yfkita2S;1XP)%B5iCGoci(89erj1U>7C2pnGyovDVR2t=Pw_`WSBhgY(WRI{vl}ue$lx+C2j}nghFW zqE|s&l57az}DijNk%{?{z$+)>xxr zUf@v3>~TsoI#@>UR#A!Xm|F&3VqbB7m18xYPBAF!tOnXsX-x=*iv<9D`=nzig?5%@ zEFZtHUiz))?5p~lSF;$VaRt1i&Ai3?)J)6uZqxWS^PRO4l zkWSqrrOWroq|?$b@q5+scHG0H)f3Lcb2OdA_yW_}I%sC1yai~50a(T$?5^*Lqq(YS zovM{OR_a^8P9*~=^bXyKR*KvSYz|O7%@pH>0CzymDXC!fFVOd$va!osvG5WQ*2%860h}q?qYbauv47Kc43h05D2q=w)N6gikPi;&&e(U<}{|6|PZ=*JESG6N&sCJ0!Ccu;}MGqlfNg>AY9y)}Z=ovX}#b?X2m_`vz02K< zw08Qby)lGLv;CEV)2HRMud&AK>+LmysR(Q{2z!(?N50PhUeF(fwt2wec#E|1agdu^ zJwRiRC+BNYJDd)a5d)#Ww07J5d{`K!Oo8`5VO8s0=^GJ9jgMR<3o95e>1vxT1GTBM zl^D_G&F}cByx381tOe$w|>KB)dSe7!~|)^lG2=jT;?P z5i}oN(tgI@ru+p>7GJAg7x5hE7X$8J4)z8d+zp*;pU=J8jF|L2Jv;4ac$TR>NiI8H zi8EvBc{R2pOH7wU#78GR)ihm=fz#5NPNY6bEtg)E?e^TG)`j5Y8n{(cBKzCGf2!SX zehYR%sj^E7`aX5*{cridCPDj5nrRQ(ASfHSJEyU63rC>$mUcfmAQ%S7!BhHF9~cz$ zCz%(mCh2ZZ_Yw!+AKkjY%4IcdZN+i=-T(^a zQwEK2k}>{e@t9T+OKpwu^|odMa4}%@6aT9@>hT4OoqkQ0IWJ{@9^R=437r^X74I$n z6?i0_=NJY61WLZDlekLETxu{$b{~DdUAUIpxDq~>13G%sfiYv?1bYP~kSym7llC=hqD>;#qefzrJqLsn)bhP_w9{F|UcG~7 zh&2J9IJkm(@IeuKb*BC9{7eYpIed> zg$!evOSMxApCnx1lM#gf3JokPtJdtUGlYb86M$BufxZI<5srw}@~4I`!UBEwMyF=y z=0eaF>NK&FNj`LQx&6)(5yxL2u2!*N0r9!jOf##*OFN(;{*f7|rBvAZesk4G)t8DE zvqjhfKX{o5pqHlF2M6UdAzmzaR?|&9#sFV~^iq^wS7}}Nv&~RB6jnjuPz8E!LxSQP z+XCB=d^fn-;l=62Q+R^jZ8?pq_g3B?p*TI`k~Bs4B}U|L3w(S+GGHAqZ3|i;@5QA5 z4v7s1Y742+-)M)WBM0Bw5F_mgp2}=!tm7Su;1m#pdNH6z73l zlzdH@>5^lbur(usrlZ2t(ijFofn$s)^790-wYN zg-e#{5)nSsG8fBkVxOT{LaDh^O=7=vs3dz3ss$l=ty!Re(SaAVRZIp)*HCH>y!s2e zQXl-+OX>u{^CQcLEQzIHizFy)AJ5pMaGmlIg2rB6Dp{sGVd_+A1!?c3TPs554bYz$ zA9ow;#GBLX?27aJd=SK+4YB0!6O-(W`HA)nxx^JZWV-Y0D`zm^-ekZmfqtLz4)bMBS$K}l8 zctiw9uhW`V*r`U-+JU)~lh!RUC3-JbP(uguwU-zY zhM``V>5@5Cqn@WvyAdW?+*QjH9iL^RWa#~y?m>?!% zgtFS?teh5>74Y)CX!sMA?)O651y4OWxdWH%Z){ITEJrw3Oso%cgdksYVmtUDVnL@A zWVK;eOeR9VLg*`aBI`jFR1lv))f+@)H5n$8Lf?*Oc0+v954rV*UD!Wc`NupkHE@Li zN{E|P1S&?Cg>5oCNREg~nD?54)g6xuEG;(q720QdnEleXyQ3A+!vLT%Ivn%R@*?{rl3cAPpG6wAEgxs!J=H#CUwr{R;!*N?gA1D&VYNd6KOl`BSCCQ}<>a<#$gH6X z;^uBTG;m@#OwhT8Cz!Nv(YlKej!G4bY3^#8BtMd-A2;*E0|F#f14HyJklLomdMNn| zX0F91^eJ3WMAU@XWG2!+DZoSKZa_Cmp&Qez7+N9M_d~0VG464|yM#|pvc#anoO-gw zyOoq_-yvMJQJkX@Kn$S5VXW@W3y-rm5NU>*QM{d+2ljF$2L!BYD4xv`^MvR6n-f(u zk)HToAM+;jvU_Ay5;g(CzQ3m<-N+T4Xa7xyRF=qU>BI;Q+PrP)_$Jhe7xb*Mgb@tD z;vv9xj_#Uz_`@(ajXi9}S^JYo1Rv{2J;t1pC}sj6IKWPNZ%L}u9LDt;Le5juM~&XV zI(3gcL9(1pM_Wgm;L_W%HmK?Iz%D?QL|hFgG@`gjPzFbMFefiwyHP7&RekO4;~#*) zd&OJP_X&Qbf|zFPL3zqJwkGMh=#CL{x_9()+l3G>xLQU7X0Gy{ZyM_T|ZIV|Ia&(G-xi<0nbIuD=L++Woxa7zC z7>U=Ho5mlp)@ke1%30C?6Z^K~u@;AzSKWYHt|SS(O`k;IyjK}v7PCnS5bI+-~g1~M-SHDzAphZdy=-8C}W|2J8>yUhH^B^Tb`K0aBWbsmt820*gojz4;*kNEQzeSK zpcHz^=yF;5_1O#7*xC}aTwJazM^F-{8aH-HXAr3tg$s=$3HuZR5g1t zl|VnL*ZAjYE2f&ARZ>9ZrEPUq-&&XwR;yd$Oc(p2*da508-#!ntbJF$S6T$EI*=>b zMVSAKya<{r7LT;t(3|Awk{j+lLWyoCvj?j+Q4~;;!c-DkF7R^ZCw9uu;V=hC*`Un% z%b-ACK=T4CbTWog;Av;$o-ESCC!K0ccSP6phEpk0NowD`gKEm}rVP+wjn!^cG5gzP z*pd;~!D&jGd(0|J1yg*~kHsuYg&rVZ)MRF)Fz*a)p$=5&zH&j*sO=%yDTIY=$x}F@ ztMW_rAk7*!E|krSyjR0DB!D{c^l$!ZoN%QZ=O@zPJ8~E5f^fU5E%B`L(348X%x81t zfYV(TV9^LZBar4j% zu8Tg-BuMBN7rs0DEfJM&-nDv97i-idd5&P6(gv}?*nZz;Q3pNn9hr6gRI zu>NST^lHx?i- zGHRfJ5Y&*PL=Ak$KG1Ejm4fGII5y8cA&UCZNoJ=)xA&W`ipgn2+tQM>=rbE^h}*;` zBNbhXeQj?P7r=K|SMyb}R9_S9(gx66Cr+&4tD#l!S)4q+~$3lgw(zg_hd1rQ5 z$WyBR^8x1Gur`#sr5D@uP^GT;sGARdP{P+~jT$oo40mT%O*D#ep+CD-qKk*0;$n&A zmu8BnyBR%(f&i%CEUrf;Of~^2Ed3sX7W<#NPs3s%Of71-FgwG-_+lxNGa9a)v zEm=u6seN@;(+ThDtP?=DeSH|Wmuy2|EKeVLeOERAF7!Y^ljoi0I2o^QSAMozm+K$A>=Goof;m zZa9+PXe9gW)j9r!0K?nT-E%t)4QvmUnB#Z}88?%b)N3V*`oGj%n$?g zz!5O6WL^@}9(i+;-#N)I`^W%N@j4>E4lkB&-gJNTy-jn_CB#lg1u9$U0)fK)69Djt zb&{vzPi6Y3X{TnUkaaE!)gZ}6t~ztka@t3>`LF1^vkCfAg!l8SfmMFLg?qk&I;YbK0jgT5?ad{<9<@LlGMjGS$>}-h9gb*vp2)1 zTRa8X=X|L^pKChTplKQyxLJa}46x}Y)%MBd*w~}UqSy4!);?WN%*fQfcuU+2C+K;j zWgrA>?A$>tR`#uRGZ5MLq0`H*R&4VwvzHox<>`5GBT?VJ7-sclZ;7C%YTjGGNZkBJ zkO6%!?Q(C%!KV7}*CBvPjYrM51O;X$xYm1g;_*$n@2YJUGLxkGgvb1J?LE~O)tLv@ zV{%2Lj%gspY&n$kY^*|r@KgT?Rx!3U$|vmNTcQcorz*yC0^*-m1w)W^?z5T*v_y%Y z|GtA4?|qco4Ao=XpE(yJm5&Gt$n`n!^!Yo7>kj?>3V zC(r#U&UUoOgbi#dD#&~j981n7(`P>!i;PJ@m@A1VUQ_;JHq&cD7fmZq-(fd%aK5vw zVCSj$lbR`xXRMOXFu78Bluaq~3s^k&A)NqF7@NY%@QF)i*Px(d{^W}I!di3OssI-V z$UE4$!?hcGTY8d7B-GNlwyb3khBPf!X8*<n1e=CSPSHgfnNUntqWqby$1%U5pDCoul6z zwg(8IRM%ZK74c*ln$gEoya6$SR|KZ&N2!ABM=92=c264GAKIRR0fCY2DLU^{&Hfq% zDRUj4txTrjy1Tg*rrT&`?BR*yg))%Aij;B?_OGF*zlxFimyzCh@By~B;4PjB56i)M zYUG3J&E3E(tS3Nc|FPWvkLZh^!^yVoFzb1|X!|8+$V#{`c?MS0rt4%AebsEvM`r@9 z&5fMV9lX@so(J+{9#7dh5*Y;lU(M4KrXZvyL5-zlv535hF(D!saO=SZn zqLG}b7?F*CRAIsAPeq^m-OgnN5W_nowJEr2`ZvzUUp{6DC?fCoDx(&fye#LsbjbX( zoq+6X`_|MXpV0F3WnY}HX;e(Q-+PrloIfOXmW)=Lv~B`-FpE5U9;oy0+fLnlhSW;j z+K&XIni^ta{7u%ql%O6`w&Zjw_NtM$p#WD0qu0q@aZUmI&jLN;H0lMV0mh`!`WD-6 zwoVgtn`>VePeW#^s)w%)6)wnxo`GRdqbo)|g%kMdvUQeT;qlA0`GS{QJ>t|KQG zyUt6K+~ElCQPaMe2`m+R#S|I$e!inAtWp4v@!-=Z@qx$x_}%t;G=KkPMmsqm>*nxSd>d;n{sZijTR(b?#gp{3kn=lxrEyxrKqRW^*MG&0z=W@Mj<-+KWnwF5y} zG7YhdPs&daSigeOE#kC(0l&=IaZ}|n{!V_0-AlT!9gK_rzDMU)sV(kabGD=pn1qKI@#hm^5 zJbUhTxBHx(^Escc^GpqLDqCFrh#U&oMf~i2E<9C+L|^S2$I-J3d*2OJ=@nV{`dR<& z1WnN{yYVYu5y#m2YK9CE|Diw);kz5{Y{ry@e?q&`vOJoqT-QIC$+l|IK=P|=Y5Rwr4`iO}DBs1Gq_SondgkeID=GIowP=^HX|z;(GNxm|LnfR47{+%z%T=^{7ITkH6& zSzbZ(Iq|n(Y!;&UTH@6#d2|2iFh*7VbN)S}vQzcT_Hu23bjA9OY&DI2ja`9}V0>#_ z`=|s;O7RTSUUtixX;@LJCc;pt(M))vh0P;KkBenF-NnUa`jO|aEBzgL={f=oj}1k$ zGm#;i4Pvb_(gQHVliqfJWqZT~lb+qsb>?fh+=uk^__cFgPsswl_P=8|Uo`8x46*OF z+=SkA$qcSsk~LC%x($iGEahG|@am2l1tsCM@CuB&UIiN3O(z-todi?d6d7G(7@-A8ux|BGf+qZE%-My@i3zPk4cjli?7{ zhaO%Fk2X>V-Ftdy=x|YklhPS_G{AU=v}mmI=BR7 zrGKAynAqxG;u1F)6Hx^sk;=C1HBA?Zxf$4HQ4_Qhoe{`Ay7ljYM+; z`7~#I?az-#Ku-)N``J&>ZPj(__`YApOzWJ)PeDp!<>J(D>V3B{?_0}s6716-j0j|sYPffF0xD8uO{bk@~iigT!@+1V~2l#&EJLcw*@tSu5EfeTBfdW zlO{5w%mVy!`Q{(V==QW8{d^y^mKA_6$^`V(Kf<4a!)qH z-9F!$YF1Ephq+@YBfZFpRW38}q4(X*4=G@pKT{v)YBKz<=VLTc=g8mQncUyL;=Ol; z90tVjU%!vO33geLbpSP_J4`E^WQ@`QsQdO$z?6x9>c~HF@VTZ-|5f@Y^e)%5t@2`X z6DxvOnJH1MhT zV7U~dIw$MGHll|F+BKGib@mi^YU&I2hPoPAR*d_lP$BW_E*1mhHE7SGEt3|lB16G# zauxpD6Vb9n2f0u5}4}pZY{o^sEx1G{pk?hcb7gS=xU3gC^DOh_&xD$ za9$hGJag4O<@eh)HBWuCG!ylC-4CvLp8_~lzu_Ie@*QqChTOxdavNK;{PA9~dL?v{ z-NH-7A&dS<`heDZ9+APCE=N!m>e+m*wJ#Ls`E=nP0%TfCu*>+8wNpba6!OZs5k^giBPY0Xo~F0oET8JUiBldDP-1X> zO&xZ)Gx^%7m5+>oBx6{OR#sJnVWk`v{!7(^z@PSqnkJ}b`Xi(&e%VV)K}7a`YH%Mf z{@h!Tmjc=@o8w>Fp~CJ!@6ORmy7&27+~}-($Lqreg=zS#Jpd@w1*q%CH{ix4y*Z{kQghL8v9cti zAo#4k$~UZxd=S+B=p0rP(E?8lX$edCzp?;H6H+|-r7HU&Pv9*{JW@v3cO2bc!|eEL zXk+J>Z*nHA(C4WTPI>;)EaTq#r`6i+U?k}B7qZSmEW)3c?Y+mrwg!Z~K{bF*lA14b5AMI58t z+VGCAq(PC6$F~p}vPE&fGLPMvKtjm2S|~ZcOG6`_@aFx35t#2La@ZeL=zA)@L>VoV zVWBYHOI|a8=aBrX6apuO`%xuGS&n$6^ZjiV^{0R?wTF?5$-$jSbGCYD+9d9dTqjwY zZ9bp9sUV4YKm!1W8i(#o|Jxya-pt8E3Cb zg6h?K#sMrmRF($CeQR|uJRCoWY|CT{3@7X_$cNA|!(L&0_X=l3wlPJ=qxv3OC~{8< zoGUH9fF|_{(!Fu>ROkKVr=$n!MZ3)v?7^q5cFVMu-d)Qgjhry2qC@0XFXSaf3oC{h z_L}UUvc@W{BKeWT0e7F%l18PTxlc7F6u)@%vr3mUN#Lgt2tbH@2HC~9BV-=K%U@=H zw&*KYauQ;63B-3J`Orotk2Kpx^i-c_VQ%Y?itoY?$(dNssT;sb}}=&!kWCNpS@#lVr&2 zR}+7i$n;DZB@vky-C81X7_0a=$nJkyyrlmit^NF{Js|9jQU*wkG3-4G9LiiI5bY@~jf4noI&vcz{uAm`EfXg2j+&RF1@<&$MCM)(wb}KIsq0+&$ zNT&SH1iTYrc}U>GK*qWVJNQ@|*3}%Mc)+oFv$^g-NA~1p3jol%jI9PGo-uQeKQYKz zvy%SiPv$%IrpVRkc+-QpxAxIU2#*90n-Tt9MTcnSU4TlQ=nC$vQm_4rKTCI+PkYVsHg8Scub)x9JBA%c(dI zRupI6-eW>Qp7zB*xyrl86okyUpROpU$HpXfvDoqbodI`w7?Cd*F4R9{pkz;G%yzER zA@%9gm#xxZiDY$6O;uPEAEYSK-CLtnA%O^0`n7u;$Rd=!onVvlXk)s|ygOmQs!->? zmMmNj`|;uz0tFTIZ`8M)91~qCw`|XMsN|Vl|7jkIW5?I)vRih`ZRHF61VY~1w#!fe-p>px3R*s&QPKso%1Gwi#7f8&bXA(ah@csi?ihHmT0Xspt-GEVBt5n3&1e&&>{`{OJ@r)jQ zL6%MgfED`gM;9OS*dO;*0t3o%XM| zavb>n!rQ%0z(^-NLZF`*tp}rY`Oz(Ne7tjSk)okAEE#?8LYZGxY&juoyVxU#&_(oW zFC8@0Q}b6tWE&54DiEk^F?gke+R1apXn40{kNE#yrZ6LB)xuZLR5u zc}|pBq_>4FX-M8l5ponp)VDTXql$0C#h_z%yJ;ZrpxsvkUMpzv3VxX3acR8I&bw9D zlgT~QBwwZQpI)<&C&zafia!>|{31$GD;dSh!!05Y{Q!95X8536NEL0%C@;h{4;Jdo zKwi6Dv(xmOatI=wpWf2pn5p`;1sDq>wY8QNG~3kunqn7^rmyP62d}j7H)~6CfODF^E4OkPM%uvIaC>fGDUy=e;U%GI`SY1DFCv z3>b79N+AJ=vywhE{{^w&PTJJp7W0bo@|Rm&>a3i}!?z(>^n%v}UsNiUt(`YYM|WoE zSYC7d3PXeu+j==6bBn`|%-bszOAs;6D$9@9hjkMkF zjy>!V`$C)0xZVBR@Hth8CXj6o0>z~z(q@~T; zqSByXU6DRj>Wo+kka-&B2R%1=}q1#akLPIW+%( zPd_fI0w|!1Rc9KM-E9C`lZf4MfPEZG(m|(2E=DN^C;zZn+qK;{>Fv>8nGYeAS!=P* zAO7Hv9*TpYXb@_o7Vd_tO2qf4w-oRSM+cVfh@tK=BwBc z*l#(Y0k5+jY*V?uys)nJmV-52S7I)wuv}xmxCI{|zajSEP5LMqT74G>;J55eN%eau z0c$HTvzXGQL5&?e_AfW|4&?Y%W*}AlJ^%jo<8@!kRXSP5K_U0>If1P9a7NhTL|5S9 z#`T_3P5q52a~j?QzL5jgu$nF{}4>uIgumW9Kh}!>nuPt79ZniY>h7|%4U3q zWWK#86WvD6kLH)K z+X>F93JUEgkBDZ_GJbqy1iEs^Zn6rNrFdT#58%p*_@fsl(4iPyjL+fKPP%04Z3r>h zI6H!C@PNa!joD;MP4{n%=h(g>$cm?;)K<$8Wb4txY6YrFqeLfMLzg4X{9p#1mk3z1 z5|wOSG0aLRSyOM0vuXNtf)M9`Y-1$EVA2JvC(M6vpFPFynSanEe9UI)2PxAoCx#)M zlG0-NK7j}zjg&EI<6SxT*IXCC>3Vgi?P4Rz?lhd?%i~B-e~@`xGI5Apr#Pzla2Tma z)Zd(XOzZy^uwuq;983Ubek|Bhw+6+Q zN`e8;jSmgj%Y9E!V%8OLN$ho)kS?GY+Q^GsI*zr&mPV(T7!NlF$02tLO zI7%hK-dS(I^nDI&Pqxu)1ic47~G!P(NFJwrJrzbab(3W4> zo8qyNFUUUW&~^VRpRV(y>u;uF&eRVGm95@PwaXciF)vdvAJfHE;8|Wdn7LmmY28#i z8g$p}QP?L!Je$ka1q{AB)2s%q?kPjOtKJ7@4&$W7-||VHra;H*sL`mGbZ_=u4C-HG zf}NqNKtV5^t$$#|Wh}&TrB`A$x_m00#Ez@%Pii1oCq$D(Nr{i5%`rW?UpaNg0i)ku z%XDibe{&rt_vwqIKRR@&f#D>g?|ItEZ~?@XvT=0dC46zWZy2ZOcP?hkqqJO85D)${ z!EODd!KZCA7$PBV6pY?<(0~$_2AxH`=VP^r^30uPkD#SCGh++_`?ZymveVjAQ!Ws6 zNVV5xn&Uk4GciXO6Tl{b$&~l=mBf=~TNa7+I-+C*N9-^M*RyZ|hRVF(uh9k^^uq3& z%5~7Qj~|4Z&L3v-(=_@hXaBu$W!k;Lik)!$$vvO;@wpIRACQoEB1klNe-_QGPNdlK zQy7B#EcABaoT?w?@faxh z-BZampbz+odf|i5KS=bAH(V$TOM2z5r>wsK3t)%!8v5Rpucvq&|LlD>dh+&S^9n2b zHm^g;*Z#==oPIc6^*PC1mj2IV=6Hm*9@r&=J+BQb{+ZHRZd__PiB3klXNe-k3R-dJ zhw4`>&49HmJ6bIAhfxDEyEZom`X;>p8`^oEr z*!GlpI@r7*zLT#N@**o(I^Joh>DGh{rN=F2WirT;a<2=>uxRT!|8WF(Z<;CUPcIlf zZ#Ck9fmt6FQgyL+is>F9Lhz4W4=p_M#@fFIQCo*57k>&x&;9AUoF?V-x)`0lv+G(( z4f##%Gu3CR7U6YetljkKv@kl z?;(2vwY61ALN_iv*TRmyRE#A9|Q5lnYo&>1PN zl&r27e^ET?GNH%If|$RI+Nduh_;r|0D#QbK}4IH%n!k{|khCGXD=?=nCXEAJnd4+hZqcle(_Ab2Ki z;`QKA5bPt2Q?2#h@52JqE2+3QUb215^ezWR0RP7`l>Yr zBT`UzT*8uE=)RF&^y<6s8abYaz0pj*BhGp!f90M}U1Uc6Rj8n} zCyncsPRAA2^(zuUPvWRCeYjze-5ABW)HR`JLjIf}q^z*2nqTWlo~eEkC3?G21fb(S zPEavu{skE@rEaN@m3jAkB^15fR`DeLOTd`0qT{Yv=iy^XePMArtc@@whML#%`W;Ow z(x0n18iqNBQ3)LJNJqwZo7g!{yC3XTid>0}vQ&Pj^=N2LDc`AK^pZrckSJL4vos?IdC(_Cpn7+}amiP6zktRl{(>Y#^hpe3H z34Gv6oAhe{2I4BZ2mtqPLdt@A_e~0I1Br?!QCAL7NrDW1_T#ymA;%!=k6v0-;hckQ z@1*q-6-BLJ%$>A7@1&<5;lV=>goYySI3*+b5vvRA1}8iBPht@MT;=yeu_Pw`ojd`I zK@CMl=cI}BFBJCsT7Vl)-dbBjtmg{-sc7)M>2~O|;5O(peweAO;to^Wgpp;k`#tfozH%c=vE6Wfo*vGVyS5blKjsf&^<5qAzsB zr&QlTdq9a1N!wbvvYzq12R)Z$$$K+AtZjR)E}E~3H^_?eqWF{rA>pWsy$zx+t?mY;3(~{;qT$CV`TZ3=JG61 zDdI;~{$qmTz@_P5*q8*_Yj4E!a-H0jc$^R_xPXw*vVmjIH$L!!oz97&#ytoEXg`s8 z3^hEWf<2_~WjWR`T}~D3*OO-~Nn=1qMl)Jwz= zlBun6IX@3~I3>RSk&&f7t*tir2abUAP+~%`%fer(=QJ_pJypan8&7rKnwHs&isy;8 z5VjPVc8#@+hXjyblKI)WFvk$rYNI7U zJ!tn0lg`hZkN6w@HpQO|Ue~6wlOk7ik5@V8E!-h`w=G+Nz#*p~pj#<;Lyh?Wd5{=9 zT2`+^z(oI!r802)V(m*=jW1HLAMb6Y&DBwbb+YYv#a8EmM1&gK>IrZuVRzpKLh{Kg zPewZW{qXmo_p3%M2nn~ymFr^~tabfMIJ-OztZgG)3Q9J<15)Hmhu_%?Hww+l{%LYj z4u{b&%Tbcs4~*6c%CQN;LS6#rzk%B5B%2=J459V8cKc&tGm7t1u^|Z$@Nfc7AP7^dz5Oq%7zxJO?BRXOVZx#CjPs zlpe1S8WT3xI!%Cu2K73WS)Rz|j{|rZbLqpEc+*6?+_trQ4Go7hPSbV(Kw(skl^HFU3*TR@`z_j#NUexkIupK!LIhvd;V>@q-+IYr9>bg%bpFZEY6V5+U zE$OnD7G@FcSNX2kRyiGSR|3rd8*0cNJiR{N!X_KVq`O{eOWv8M*|?ycX?K%qod=2d z=T}}4S8O)%y*%Aj*0jk>I)LK-vz!4>Yke7;SV^^-5Df(rQDC-Ep!%e*2;qHt&h+b? z0{GlR#VGD4ySF%2N9+yG=IVxTi0HaN-cJ1P;Ic2|}k{IFOTs z5lnl|b?kVBIwicM&A3tV-+8~aLqnz0;A3T#_xRclhi`l@_Na5AUE>0}s>y=7L!a2~ zQ8*`Y;KQ5Q?z-Qp(&VHToxq!&QyE)7bVZN@xRT1BOpm%|vMwa^EV4D+vfCdPYpIFE zR@UUZyy7&%t46=Iy#jL+VjY1?yv7wk zAGf~`xMD$juU=d-q{UzDJr~w1omCQNpSIiKMZ36EA#-HJ?s#vPyd@F3cStDbI^XF9 z+${gS1yYtOrN1jFVtEu@eK+f5$1;`^HUEsFc6$e+o6ON*@nrCa$OeXDf}`|3NMzan zEShj@bG1G39YSVjYQc1Q|1iRoGTRcLg{!{2;&m0LX*ke`UNBVHehef)PdVRRv!dHG zcXLJ#1@re-Yo^P+9vCite7sG!aAm`EC4yd9-Y~w~We%cN-;43bTZumIU330Kx9Xas zwd=_9P9Q}S6zeYg6&mfnJqn*)-fBV@ycCH&Z}HuXo^lvVxm}GjUReMutUn&2cU2dU zub;m)eI;Oo_loiRd&q}!9ed)AFM2TgI*mw_;AjWEPFF%>!QhT^`TL4=AMNRT(T)470&wNkB zaqySeaJ+ZSA0F;?RTg#bpC6AbiKq8Qf=GmT4{wns>wZ1M0{WE?eQ7mD_ti)?@AW;| z`)Su!Z{SkRGUd;6Uw6PM6}ieGH7@vSM2;aR?S2I^n2W~ak&+JX7d3?8bVjFaL=^T# z;EFC}cLDV<*kbEt+f=ovs49VfR}1_hiy5kwS)TEe zpJ0F(Wvx%W=hi>D7=nTU{FJBpbNM$V*G6qerJFgM6kv+g)d{>7-CiCV=x30EIX0 zX^_ee-f4}-X!RfXkZOb)*_M=T))QGi`_8oQs6AWEUJn_WQqj*)K!WHm#cVfz{ol1T zCqWPmM5fGP)e9=pobI;9Z$_ywRkS1CDb)ZowUH zgOi~~JnEiNZ?DZFo!X;QU)Y%K{2Ar3i=Vo!SP4Q4mqng2?YpGMpDRMo-zgsokpLi) zxAx-CIqM;thi8iW34WuKWCDW{TZcyTbZ?>e!_mRlK^Bz-;RUmB>7B;adf|oc9f{Gk z-aJ?fVV-(iJN%wCXbpA|M7a|KQ%mxVi>Ikk)&{kn)1k7qj_VRL?LQ2>A0<>mE_F-V zpCJf~T9w48=-P6||>Gi8-(3{sYqB;LUCvgTSC` zB@HAxqKRRq=%}~eAJ;!EfU59xpUy62u<26ZU9WHi9^;%bB%0n7@yz{+zTEHtnJ0l{ zZ_8>uoruRwq3Bd%Y+wTBC593sa(DGA$HwzBx|a$=&5ks^erU*)IXdoof~=kYxfA$; z9x_-1cvDN486p}D=QQ-B zK)wRbI-#tB8=;(V<~$)wr7d@_`|i!ZvFU0C=@8*~0#GG837-$G6$OyUu-*YW`3!kH zbp~9<1APDEjxA1{Uv!OI39buDMXL_{Z9swb?BMn!y5IgoD4saM7Y|4%%Sq$3s*A}Z zW)>W7a&R!W{a$W1NK5y~4i^jRmz674kAgi<5s~!AYlAc8xJPaF&^C>D*8){b)2Ize z(~*gWnFF45vnPPl9r=^A&`BKOPsrEQCMGB{7J1Yiipx>cek_ErH`^r}nUg)pu6C7f zAa<~lUMu3g@k&Oqd2PmFDetC{fprPpq@n)sG8iuMI<4X|qM?fy&{tTGIh$z0Ryb-BP zoM5U|H}XEX)5Rx!%WsGFsm?d>7WRZP-bYY?lCak2s$$Pa-1A~gkbnl^FEqZu5_AO| zK(;3|{rZsb~g_vJS4!x>#v`u0@RNn=`ZGr{Q0fsC0a^{Nev zde)5DIRgEYREQZK^29myx)4yLoS~OtLlit9m+Ia%q6z6OX*wG=Z#r$g#Saa<|NCt_ zpW;{~BfQxcAz1WjH3lh?wA@8}eZC=~5k+={J?@4o{wTzb@w?mV4YsZLs$%ZNMIyhR}$ z=K-#-qyPO`EIT+Vytz5D9R|Mp_AdleAz(5He|m*(Ga$a_J{E9x;XDb{2-Y68KNP1dYLCwhM zXuQa_MUG@zN=lxXa6A4T+Qx};%MH1Ac6O1LSMzTgr~o#0cB*$mgy7Id0=E<*{w>O% z3=cU_pjVS5*PyZcTULsnUs`@LET^xxmjoardMVtJl9$svC*G{sKsJ^glC?T{I|^f z4c*;E`3b`CnBu$kT3Y-=8ShE|+gI6m$QDzdXE_a@OSNDz*DJMW!#-sqo`)) z9g#Z_=^t9RrY|lokg6LS8@JD2@x~_;N(&|90*eP{)lQ?%8!$5$7xHp%en5+hEL^m( z{P&tuQkV|^&bU?F#rgT^?%Zz>66S(QC_-6oN0j`X7D`a<__x~IiblYcoPsl5AR%%}d7>Nl`klUL@^YJpVFZ z%^MWvD?pI8Vt-n_Q7tYe7NW(~YM6xzry-Jcz1tMZD3|Eh)Fd-qWQfzS+kD%I&6Mr=iXVj|U}hY#hlHI;PYvbkbEX)1H}9cURCq&w(o3kYTpS}eJxl~}(8e@xH; zFSPAUT_rIi6ET&v?HX%NZy-g%isKd5XZ5zj>qR`^PnulGJzPMXrm{tTzLRJ^;Ty}_ z(oRd*#1r8jio8J!pQ7Ewfor{%h*3AIl8<*lg{9OOUHQx1R>ze zerRz;?OeqCF;V2q1|oMZhDJvfKzNDwv0y7KuFj7bnHLY-M32Xee}Euc?r_%Qx_iq6 zE7a57-Jkg+Xtkhb`%4IKHM}k- z?T%Xz=}e*V%P6nV9W4jJ5ysb5!ea~tKY?@SXI*m8$=2k|)#Az(@|F3a^{H%xD^?jP z7_A9@l9S72JN{bEHgm%Hj|AdOH6+uu<4kee8c(i7U+_yv(1rRR9vv~I_YKa@GRA0% zI4r_}$c^eUn<-K8y%thH`pJ_g4+R8dDD+(o)xt>rqnMe&O$IL_s;FDE?F91p zF`Bur;vY0jmya|Rx&xi|v^w+_B1>*)dP$I#0a1>}gdvO*yl{ToGRoKdK6T zT47%SzCxA}Oi-5DYF+_BB+?@PkF(ysmjQ&a-M-_*>+hQvT(P-=xXPR?=RocOP1%T@ z-l&}Dy>@#5=%x+5dw6nk;MgajP+|QdTayXU<+$ss0R=oK?b`>guPz2G1Erghpk(7A zojZd7)Cw@)DoFhfeiy`Rrce-SrZ4TPigvrU#kx!to9Z-Gnd+G6kXIF`nAxCrE*3@Z zaAtQL)Xt8j;4lJTV{R=oAvE}v2D(}Kn^okR#3A{QEc3^^Z}-0`-Ke(03ME^_qP zv6^}1W-}+_B|NI%2SLVbq7#RQcBmo*nn3RhM|_tSZ+!ViwV6H|+*vSNIaBjTQKFaz zSM0CXJeoquOCZd_ZE7-Uo4C3IYdGF%Djy><(=k`0gpTJZB4CDzR5Uapbwc9ew6IH^ z25$-Ud84HKB6|I=RH-auat~fKv~=8xpQ@h{(s$5tAP%BZlcTfawwpc*+RdE`=IhJJ zEmJg5y)3fEcA7kh+o-Ns5D`L-Cq8Q{vBu^Sv2$Q6Vn^N}HBkK2Sb-~bCc>$ueed8i5pME)hI$*Gq&2a*`f#PcS=W+J}Qz%@!q|KsoL|BI`uR=%hYkF z6p~+ZsIvHzDWGjumH9_ZM5JTxd0cmc8XuGWKSd`ls_hpPpP2q@R21_4n2Pk#(NPou zYl`akHIS2W(Y|N|c^i4i$CW!E(?b24BCJcEI|GlV0_BBpOuBM5Djf6P6YR$AcX9@z zr1JqjdL8aRlBAMN5?YjOX;>^=*yd#H+Pnk8KwWvQZ5HJ9oWU}PaEZvim1HPMz`~uw z_x#L>(j0K#qR=5&Af*4>^W>q`RSuv?hgDq=cOtJ`m?_|+BJrI&cq=bnyx?#0iP6l9 z`68Ps9likD0jbq`pSEnI$?=~6O?-w|&g!YfaTldS9;?i%sT{Q+BEvC1aqc~2cKrLq z>%s3Mjiw-Q>UMcC^LD#TEF<9ycEzv#7eujf;0b~39v(U&N+{8w+O@fsgeo}T`~ z%Q)GYv29*l*W${?$H#vj>Se7HC+V~r`AXTMtK=aPcV}>Luo~QCWljk(v7eR^z~wWx zg$ocz^ZiboM7J#Sz08iwN3J(F^ZL5$SKR0YF9KwnDy^{x;(f`9iF=^FGkxGS1HQUe z4$?Gpzr^Gw9JIy%`5BYz-{&<$y#DZr;ubCg+Eyp7y<1Z70>q`;CxeIBTF;r2+X1q> zubVu#m9yuQ+i%TXe2w{AVQtw}a=5toRS1k?=qFzgOGiXRY@~{PSl1VXqQ%+%>AxZh zrF<9y6~gW|gnBBe zp&SDcx;rWvBtZHcOOAsM1_<--`KD&&YnUqlimport grass.jupyter as gj | +| Start GRASS and set all needed
environmental variables | initGRASS() | gs.setup.init() for scripts,
gj.init() for notebooks | +| Execute GRASS commands | execGRASS() | gs.run_command(),
gs.read_command(),
gs.parse_command() | +| Read raster and vector data
from GRASS | read_RAST(),
read_VECT() | gs.array.array(),
n/a | +| Write raster and vector data
into GRASS | write_RAST(),
write_VECT() | gs.array.write(),
n/a | +| Get raster and vector info | n/a,
vInfo() | gs.raster_info(),
gs.vector_info() | +| Close GRASS session | unlink_.gislock() | gs.setup.finish(),
gj.finish() | + +: R and Python GRASS interfaces compared {.striped .hover} + +## Comparison examples + +Let's see how usage examples would look like. + +1. **Load the library**: We need to +load the libraries that allow us to interface with GRASS GIS +functionality and (optionally) data. For the Python case, we first need to add +the GRASS python package path to our system's path. + +::: {.panel-tabset} + +## R + +```{r} +library(rgrass) +``` + +## Python + +```{python} +#| python.reticulate: FALSE +import sys +import subprocess + +sys.path.append( + subprocess.check_output(["grass", "--config", "python_path"], text=True).strip() +) + +import grass.script as gs +import grass.jupyter as gj +``` + +::: + +2. **Start a GRASS session**: Once we loaded or imported the packages, we +start a GRASS session. We need to pass the path to a +temporary or existing GRASS project. +In the case of R, `initGRASS` will automatically look for GRASS binaries, alternatively we can +specify the path to the binaries ourselves. +In the case of Python, it is worth noting that while grass.script and grass.jupyter init functions +take the same arguments, `gj.init` also sets other environmental variables to +streamline work within Jupyter Notebooks, e.g., overwrite is set to true so cells +can be executed multiple times. + +::: {.panel-tabset} + +## R + +```{r} +session <- initGRASS(gisBase = "/usr/lib/grass84", # where grass binaries live, `grass --config path` + gisDbase = "/home/user/grassdata", # path to grass database or folder where your project lives + location = "nc_basic_spm_grass7", # existing project name + mapset = "PERMANENT" # mapset name + ) +``` + +## Python + +```{python} +#| python.reticulate: FALSE +# With grass.script for scripts +session = gs.setup.init(path="/home/user/grassdata", + location="nc_basic_spm_grass7", + mapset="PERMANENT") +# Optionally, the path to a mapset +session = gs.setup.init("/home/user/grassdata/nc_basic_spm_grass7/PERMANENT") + +# With grass.jupyter for notebooks +session = gj.init(path="/home/user/grassdata", + location="nc_basic_spm_grass7", + mapset="PERMANENT") +# Optionally, the path to a mapset +session = gj.init("~/grassdata/nc_basic_spm_grass7/PERMANENT") +``` + +::: + +3. **Execute GRASS commands**: Both interfaces work pretty similarly, the +first argument is always the GRASS tool name and then we pass the parameters +and flags. While in R we basically use `execGRASS()` for all GRASS commands, in +the Python API, we have different wrappers to execute GRASS commands depending +on the nature of their output. + +::: {.panel-tabset} + +## R + +```{r} +# Map output +execGRASS("r.slope.aspect", + elevation = "elevation", + slope = "slope", + aspect = "aspect") + +# Text output +execGRASS("g.region", + raster = "elevation", + flags = "p") +``` + +## Python + +```{python} +#| python.reticulate: FALSE +# Map output +gs.run_command("r.slope.aspect", + elevation="elevation", + slope="slope", + aspect="aspect") +# Text output +print(gs.read_command("g.region", + raster="elevation", + flags="p")) +# Text output - dictionary +region = gs.parse_command("g.region", + raster="elevation", + flags="g") +region +``` + +::: + +4. **Read raster and vector data into other R or Python formats**: +*rgrass* functions `read_RAST()` and `read_VECT()` convert GRASS raster and +vector maps into terra's SpatRaster and SpatVector objects within R. +In the case of Python, GRASS +raster maps that can be converted into numpy arrays through +`gs.array.array()`. Vector attribute data can be converted into +Pandas data frames in various ways. + +::: {.panel-tabset} + +## R + +```{r} +# Raster +elevr <- read_RAST("elevation") + +# Vector +schoolsr <- read_VECT("schools") +``` + +## Python + +```{python} +#| python.reticulate: FALSE +# Raster into numpy array +elev = gs.array.array("elevation") + +# Vector attributes +import pandas as pd +schools = gs.parse_command("v.db.select", map="schools", format="json") +pd.DataFrame(schools["records"]) + +# Vector geometry and attributes to GeoJSON +gs.run_command("v.out.ogr", input="schools", output="schools.geojson", format="GeoJSON") +``` + +::: + + +5. **Write R or Python objects into GRASS raster and vector maps**: R terra's +SpatRaster and SpatVector objects can be written (back) into GRASS format with +`write_RAST()` and `write_VECT()` functions. Within the Python environment, +numpy arrays can also be written (back) into GRASS raster maps with the +`write()` method. + +::: {.panel-tabset} + +## R + +```{r} +# Raster +write_RAST(elevr, "elevation_r") + +# Vector +write_VECT(schoolsr, "schools_r") +``` + +## Python + +```{python} +#| python.reticulate: FALSE +# Raster +elev.write(mapname="elev_np", overwrite=True) + +# GeoJSON into GRASS vector +gs.run_command("v.in.ogr", input="schools.geojson", output="schools2") +``` + +::: + + +6. **Close GRASS GIS session**: In general, just closing R or Rstudio, as well +as shutting down Jupyter notebook, will clean up and close the GRASS session +properly. Sometimes, however, especially if the user changed mapset within the +workflow, it is better to clean up explicitly before closing. + +::: {.panel-tabset} + +## R + +```{r} +unlink_.gislock() +``` + +## Python + +```{python} +#| python.reticulate: FALSE +session.finish() +``` + +::: + +## Final remarks + +The examples and comparisons presented here are intended to facilitate the +combination of tools and languages as well as the exchange of data and format +conversions. We hope that's useful as a starting point for the implementation +of different use cases and workflows that suit the needs of users. +See R and Python tutorials for more examples: + +* [GRASS and Python tutorial for beginners](../get_started/fast_track_grass_and_python.qmd) +* [GRASS and R tutorial for beginners](../get_started/fast_track_grass_and_R.qmd) + +## References + +* [GRASS Python API docs](https://grass.osgeo.org/grass-stable/manuals/libpython/index.html) +* [rgrass docs](https://rsbivand.github.io/rgrass/) + + +*** + +:::{.smaller} +The development of this tutorial was funded by the US +[National Science Foundation (NSF)](https://www.nsf.gov/), +award [2303651](https://www.nsf.gov/awardsearch/showAward?AWD_ID=2303651). +::: From 2f3bd2b3c8cb835ddb2060659f7178e7ad58f70d Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Fri, 27 Sep 2024 16:50:45 -0400 Subject: [PATCH 2/2] change tags --- .../execute-results/html.json | 8 +++++--- .../quick_comparison_r_vs_python_grass_interfaces.qmd | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/_freeze/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces/execute-results/html.json b/_freeze/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces/execute-results/html.json index 085866a..ee88b58 100644 --- a/_freeze/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces/execute-results/html.json +++ b/_freeze/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces/execute-results/html.json @@ -1,8 +1,10 @@ { - "hash": "02b5f6931312f20d479fb4fbc02fb26f", + "hash": "b2f5b1384f95aeb0e0a837650dceb10c", "result": { - "markdown": "---\ntitle: \"Quick comparison: R and Python GRASS interfaces\"\nauthor: \"Veronica Andreo\"\ndate: 2024-04-01\ndate-modified: today\nformat:\n html:\n toc: true\n code-tools: true\n code-copy: true\n code-fold: false\ncategories: [GRASS GIS, Python, R, Intermediate]\nengine: knitr\nexecute:\n eval: false\n---\n\n\n![](images/R_Python_compare.png){.preview-image width=50%}\n\nIn this short tutorial we will highlight the similarities of R and Python GRASS interfaces\nin order to streamline the use of GRASS GIS within R and Python communities.\nAs you may know, there's\nan R package called [rgrass](https://github.com/rsbivand/rgrass/) that provides\nbasic functionality to read and write data from and into GRASS database as well\nas to execute GRASS tools in either existing or temporary GRASS projects.\nThe [GRASS Python API](https://grass.osgeo.org/grass-stable/manuals/libpython/index.html),\non the other hand, is composed of various packages that provide classes and\nfunctions for low and high level tasks, including those that can be executed\nwith rgrass.\n\n\n\nThere are some parallelisms between the\n**rgrass** and **grass.script**/**grass.jupyter** packages, i.e.,\nR and Python interfaces to GRASS GIS.\nLet's review them and go through some examples.\n\n\n| Task | rgrass function | GRASS Python API function |\n|------------------------------------------------------------|--------------------------------|---------------------------------------------------------|\n| Load library | library(rgrass) | import grass.script as gs
import grass.jupyter as gj |\n| Start GRASS and set all needed
environmental variables | initGRASS() | gs.setup.init() for scripts,
gj.init() for notebooks |\n| Execute GRASS commands | execGRASS() | gs.run_command(),
gs.read_command(),
gs.parse_command() |\n| Read raster and vector data
from GRASS | read_RAST(),
read_VECT() | gs.array.array(),
n/a |\n| Write raster and vector data
into GRASS | write_RAST(),
write_VECT() | gs.array.write(),
n/a |\n| Get raster and vector info | n/a,
vInfo() | gs.raster_info(),
gs.vector_info() |\n| Close GRASS session | unlink_.gislock() | gs.setup.finish(),
gj.finish() |\n\n: R and Python GRASS interfaces compared {.striped .hover}\n\n## Comparison examples\n\nLet's see how usage examples would look like.\n\n1. **Load the library**: We need to\nload the libraries that allow us to interface with GRASS GIS\nfunctionality and (optionally) data. For the Python case, we first need to add\nthe GRASS python package path to our system's path.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(rgrass)\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\nimport sys\nimport subprocess\n\nsys.path.append(\n subprocess.check_output([\"grass\", \"--config\", \"python_path\"], text=True).strip()\n)\n\nimport grass.script as gs\nimport grass.jupyter as gj\n```\n:::\n\n\n:::\n\n2. **Start a GRASS session**: Once we loaded or imported the packages, we\nstart a GRASS session. We need to pass the path to a\ntemporary or existing GRASS project.\nIn the case of R, `initGRASS` will automatically look for GRASS binaries, alternatively we can\nspecify the path to the binaries ourselves.\nIn the case of Python, it is worth noting that while grass.script and grass.jupyter init functions\ntake the same arguments, `gj.init` also sets other environmental variables to\nstreamline work within Jupyter Notebooks, e.g., overwrite is set to true so cells\ncan be executed multiple times.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsession <- initGRASS(gisBase = \"/usr/lib/grass84\", # where grass binaries live, `grass --config path`\n gisDbase = \"/home/user/grassdata\", # path to grass database or folder where your project lives\n location = \"nc_basic_spm_grass7\", # existing project name\n mapset = \"PERMANENT\" # mapset name\n )\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# With grass.script for scripts\nsession = gs.setup.init(path=\"/home/user/grassdata\",\n location=\"nc_basic_spm_grass7\",\n mapset=\"PERMANENT\")\n# Optionally, the path to a mapset\nsession = gs.setup.init(\"/home/user/grassdata/nc_basic_spm_grass7/PERMANENT\")\n\n# With grass.jupyter for notebooks\nsession = gj.init(path=\"/home/user/grassdata\",\n location=\"nc_basic_spm_grass7\",\n mapset=\"PERMANENT\")\n# Optionally, the path to a mapset\nsession = gj.init(\"~/grassdata/nc_basic_spm_grass7/PERMANENT\")\n```\n:::\n\n\n:::\n\n3. **Execute GRASS commands**: Both interfaces work pretty similarly, the\nfirst argument is always the GRASS tool name and then we pass the parameters\nand flags. While in R we basically use `execGRASS()` for all GRASS commands, in\nthe Python API, we have different wrappers to execute GRASS commands depending\non the nature of their output.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Map output\nexecGRASS(\"r.slope.aspect\",\n elevation = \"elevation\",\n slope = \"slope\",\n aspect = \"aspect\")\n\n# Text output\nexecGRASS(\"g.region\",\n raster = \"elevation\",\n flags = \"p\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Map output\ngs.run_command(\"r.slope.aspect\",\n elevation=\"elevation\",\n slope=\"slope\",\n aspect=\"aspect\")\n# Text output\nprint(gs.read_command(\"g.region\",\n raster=\"elevation\",\n flags=\"p\"))\n# Text output - dictionary\nregion = gs.parse_command(\"g.region\",\n raster=\"elevation\",\n flags=\"g\")\nregion\n```\n:::\n\n\n:::\n\n4. **Read raster and vector data into other R or Python formats**:\n*rgrass* functions `read_RAST()` and `read_VECT()` convert GRASS raster and\nvector maps into terra's SpatRaster and SpatVector objects within R.\nIn the case of Python, GRASS\nraster maps that can be converted into numpy arrays through\n`gs.array.array()`. Vector attribute data can be converted into\nPandas data frames in various ways.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Raster\nelevr <- read_RAST(\"elevation\")\n\n# Vector\nschoolsr <- read_VECT(\"schools\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Raster into numpy array\nelev = gs.array.array(\"elevation\")\n\n# Vector attributes\nimport pandas as pd\nschools = gs.parse_command(\"v.db.select\", map=\"schools\", format=\"json\")\npd.DataFrame(schools[\"records\"])\n\n# Vector geometry and attributes to GeoJSON\ngs.run_command(\"v.out.ogr\", input=\"schools\", output=\"schools.geojson\", format=\"GeoJSON\")\n```\n:::\n\n\n:::\n\n\n5. **Write R or Python objects into GRASS raster and vector maps**: R terra's\nSpatRaster and SpatVector objects can be written (back) into GRASS format with\n`write_RAST()` and `write_VECT()` functions. Within the Python environment,\nnumpy arrays can also be written (back) into GRASS raster maps with the\n`write()` method.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Raster\nwrite_RAST(elevr, \"elevation_r\")\n\n# Vector\nwrite_VECT(schoolsr, \"schools_r\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Raster\nelev.write(mapname=\"elev_np\", overwrite=True)\n\n# GeoJSON into GRASS vector\ngs.run_command(\"v.in.ogr\", input=\"schools.geojson\", output=\"schools2\")\n```\n:::\n\n\n:::\n\n\n6. **Close GRASS GIS session**: In general, just closing R or Rstudio, as well\nas shutting down Jupyter notebook, will clean up and close the GRASS session\nproperly. Sometimes, however, especially if the user changed mapset within the\nworkflow, it is better to clean up explicitly before closing.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nunlink_.gislock()\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\nsession.finish()\n```\n:::\n\n\n:::\n\n## Final remarks\n\nThe examples and comparisons presented here are intended to facilitate the\ncombination of tools and languages as well as the exchange of data and format\nconversions. We hope that's useful as a starting point for the implementation\nof different use cases and workflows that suit the needs of users.\nSee R and Python tutorials for more examples:\n\n* [GRASS and Python tutorial for beginners](../get_started/fast_track_grass_and_python.qmd)\n* [GRASS and R tutorial for beginners](../get_started/fast_track_grass_and_R.qmd)\n\n## References\n\n* [GRASS Python API docs](https://grass.osgeo.org/grass-stable/manuals/libpython/index.html)\n* [rgrass docs](https://rsbivand.github.io/rgrass/)\n\n\n***\n\n:::{.smaller}\nThe development of this tutorial was funded by the US\n[National Science Foundation (NSF)](https://www.nsf.gov/),\naward [2303651](https://www.nsf.gov/awardsearch/showAward?AWD_ID=2303651).\n:::\n", - "supporting": [], + "markdown": "---\ntitle: \"Quick comparison: R and Python GRASS interfaces\"\nauthor: \"Veronica Andreo\"\ndate: 2024-04-01\ndate-modified: today\nformat:\n html:\n toc: true\n code-tools: true\n code-copy: true\n code-fold: false\ncategories: [Python, R, intermediate]\nengine: knitr\nexecute:\n eval: false\n---\n\n\n![](images/R_Python_compare.png){.preview-image width=50%}\n\nIn this short tutorial we will highlight the similarities of R and Python GRASS interfaces\nin order to streamline the use of GRASS GIS within R and Python communities.\nAs you may know, there's\nan R package called [rgrass](https://github.com/rsbivand/rgrass/) that provides\nbasic functionality to read and write data from and into GRASS database as well\nas to execute GRASS tools in either existing or temporary GRASS projects.\nThe [GRASS Python API](https://grass.osgeo.org/grass-stable/manuals/libpython/index.html),\non the other hand, is composed of various packages that provide classes and\nfunctions for low and high level tasks, including those that can be executed\nwith rgrass.\n\n\n\nThere are some parallelisms between the\n**rgrass** and **grass.script**/**grass.jupyter** packages, i.e.,\nR and Python interfaces to GRASS GIS.\nLet's review them and go through some examples.\n\n\n| Task | rgrass function | GRASS Python API function |\n|------------------------------------------------------------|--------------------------------|---------------------------------------------------------|\n| Load library | library(rgrass) | import grass.script as gs
import grass.jupyter as gj |\n| Start GRASS and set all needed
environmental variables | initGRASS() | gs.setup.init() for scripts,
gj.init() for notebooks |\n| Execute GRASS commands | execGRASS() | gs.run_command(),
gs.read_command(),
gs.parse_command() |\n| Read raster and vector data
from GRASS | read_RAST(),
read_VECT() | gs.array.array(),
n/a |\n| Write raster and vector data
into GRASS | write_RAST(),
write_VECT() | gs.array.write(),
n/a |\n| Get raster and vector info | n/a,
vInfo() | gs.raster_info(),
gs.vector_info() |\n| Close GRASS session | unlink_.gislock() | gs.setup.finish(),
gj.finish() |\n\n: R and Python GRASS interfaces compared {.striped .hover}\n\n## Comparison examples\n\nLet's see how usage examples would look like.\n\n1. **Load the library**: We need to\nload the libraries that allow us to interface with GRASS GIS\nfunctionality and (optionally) data. For the Python case, we first need to add\nthe GRASS python package path to our system's path.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(rgrass)\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\nimport sys\nimport subprocess\n\nsys.path.append(\n subprocess.check_output([\"grass\", \"--config\", \"python_path\"], text=True).strip()\n)\n\nimport grass.script as gs\nimport grass.jupyter as gj\n```\n:::\n\n\n:::\n\n2. **Start a GRASS session**: Once we loaded or imported the packages, we\nstart a GRASS session. We need to pass the path to a\ntemporary or existing GRASS project.\nIn the case of R, `initGRASS` will automatically look for GRASS binaries, alternatively we can\nspecify the path to the binaries ourselves.\nIn the case of Python, it is worth noting that while grass.script and grass.jupyter init functions\ntake the same arguments, `gj.init` also sets other environmental variables to\nstreamline work within Jupyter Notebooks, e.g., overwrite is set to true so cells\ncan be executed multiple times.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsession <- initGRASS(gisBase = \"/usr/lib/grass84\", # where grass binaries live, `grass --config path`\n gisDbase = \"/home/user/grassdata\", # path to grass database or folder where your project lives\n location = \"nc_basic_spm_grass7\", # existing project name\n mapset = \"PERMANENT\" # mapset name\n )\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# With grass.script for scripts\nsession = gs.setup.init(path=\"/home/user/grassdata\",\n location=\"nc_basic_spm_grass7\",\n mapset=\"PERMANENT\")\n# Optionally, the path to a mapset\nsession = gs.setup.init(\"/home/user/grassdata/nc_basic_spm_grass7/PERMANENT\")\n\n# With grass.jupyter for notebooks\nsession = gj.init(path=\"/home/user/grassdata\",\n location=\"nc_basic_spm_grass7\",\n mapset=\"PERMANENT\")\n# Optionally, the path to a mapset\nsession = gj.init(\"~/grassdata/nc_basic_spm_grass7/PERMANENT\")\n```\n:::\n\n\n:::\n\n3. **Execute GRASS commands**: Both interfaces work pretty similarly, the\nfirst argument is always the GRASS tool name and then we pass the parameters\nand flags. While in R we basically use `execGRASS()` for all GRASS commands, in\nthe Python API, we have different wrappers to execute GRASS commands depending\non the nature of their output.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Map output\nexecGRASS(\"r.slope.aspect\",\n elevation = \"elevation\",\n slope = \"slope\",\n aspect = \"aspect\")\n\n# Text output\nexecGRASS(\"g.region\",\n raster = \"elevation\",\n flags = \"p\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Map output\ngs.run_command(\"r.slope.aspect\",\n elevation=\"elevation\",\n slope=\"slope\",\n aspect=\"aspect\")\n# Text output\nprint(gs.read_command(\"g.region\",\n raster=\"elevation\",\n flags=\"p\"))\n# Text output - dictionary\nregion = gs.parse_command(\"g.region\",\n raster=\"elevation\",\n flags=\"g\")\nregion\n```\n:::\n\n\n:::\n\n4. **Read raster and vector data into other R or Python formats**:\n*rgrass* functions `read_RAST()` and `read_VECT()` convert GRASS raster and\nvector maps into terra's SpatRaster and SpatVector objects within R.\nIn the case of Python, GRASS\nraster maps that can be converted into numpy arrays through\n`gs.array.array()`. Vector attribute data can be converted into\nPandas data frames in various ways.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Raster\nelevr <- read_RAST(\"elevation\")\n\n# Vector\nschoolsr <- read_VECT(\"schools\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Raster into numpy array\nelev = gs.array.array(\"elevation\")\n\n# Vector attributes\nimport pandas as pd\nschools = gs.parse_command(\"v.db.select\", map=\"schools\", format=\"json\")\npd.DataFrame(schools[\"records\"])\n\n# Vector geometry and attributes to GeoJSON\ngs.run_command(\"v.out.ogr\", input=\"schools\", output=\"schools.geojson\", format=\"GeoJSON\")\n```\n:::\n\n\n:::\n\n\n5. **Write R or Python objects into GRASS raster and vector maps**: R terra's\nSpatRaster and SpatVector objects can be written (back) into GRASS format with\n`write_RAST()` and `write_VECT()` functions. Within the Python environment,\nnumpy arrays can also be written (back) into GRASS raster maps with the\n`write()` method.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# Raster\nwrite_RAST(elevr, \"elevation_r\")\n\n# Vector\nwrite_VECT(schoolsr, \"schools_r\")\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\n# Raster\nelev.write(mapname=\"elev_np\", overwrite=True)\n\n# GeoJSON into GRASS vector\ngs.run_command(\"v.in.ogr\", input=\"schools.geojson\", output=\"schools2\")\n```\n:::\n\n\n:::\n\n\n6. **Close GRASS GIS session**: In general, just closing R or Rstudio, as well\nas shutting down Jupyter notebook, will clean up and close the GRASS session\nproperly. Sometimes, however, especially if the user changed mapset within the\nworkflow, it is better to clean up explicitly before closing.\n\n::: {.panel-tabset}\n\n## R\n\n\n::: {.cell}\n\n```{.r .cell-code}\nunlink_.gislock()\n```\n:::\n\n\n## Python\n\n\n::: {.cell python.reticulate='false'}\n\n```{.python .cell-code}\nsession.finish()\n```\n:::\n\n\n:::\n\n## Final remarks\n\nThe examples and comparisons presented here are intended to facilitate the\ncombination of tools and languages as well as the exchange of data and format\nconversions. We hope that's useful as a starting point for the implementation\nof different use cases and workflows that suit the needs of users.\nSee R and Python tutorials for more examples:\n\n* [GRASS and Python tutorial for beginners](../get_started/fast_track_grass_and_python.qmd)\n* [GRASS and R tutorial for beginners](../get_started/fast_track_grass_and_R.qmd)\n\n## References\n\n* [GRASS Python API docs](https://grass.osgeo.org/grass-stable/manuals/libpython/index.html)\n* [rgrass docs](https://rsbivand.github.io/rgrass/)\n\n\n***\n\n:::{.smaller}\nThe development of this tutorial was funded by the US\n[National Science Foundation (NSF)](https://www.nsf.gov/),\naward [2303651](https://www.nsf.gov/awardsearch/showAward?AWD_ID=2303651).\n:::\n", + "supporting": [ + "quick_comparison_r_vs_python_grass_interfaces_files" + ], "filters": [ "rmarkdown/pagebreak.lua" ], diff --git a/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces.qmd b/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces.qmd index 317300b..81b40d2 100644 --- a/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces.qmd +++ b/content/tutorials/r_python_interfaces_comparison/quick_comparison_r_vs_python_grass_interfaces.qmd @@ -9,7 +9,7 @@ format: code-tools: true code-copy: true code-fold: false -categories: [GRASS GIS, Python, R, Intermediate] +categories: [Python, R, intermediate] engine: knitr execute: eval: false