Repository: ipeaGIT/geobr (language: python)

Issue:
Cannot import "read_statistical_grid" function 
Hi there, 

I'm trying to import the read_statistical_grid function, but the following error comes out : 

ImportError: cannot import name 'read_statistical_grid' from 'geobr' (C:\Users\user-07\miniconda3\lib\site-packages\geobr\__init__.py)


My system version : 

Python =  3.9.5
geobr  = 0.1.10
Windows 10

Obs. : This geobr version was the last one that I tried, but it didn't worked with the default version that it's installed via pip. 



Discussion / hints:
Hi @tp-duarte, the `read_statistical_grid()` function has not yet been translated to the Python version of geobr. [See here](https://github.com/ipeaGIT/geobr/tree/master/python-package#translation-status).
Hello guys, I want to contribute to this project. I have some experience both in R and Python.
Just to confirm before submitting a PR: there's a typo in ("Pernanbuco") grid_state_correspondence_table.csv lines linked below

https://github.com/ipeaGIT/geobr/blob/0f829bb2c590222ad686ed42a375a3344b91afed/python-package/geobr/data/grid_state_correspondence_table.csv?plain=1#L61

In grid_state_correspondence_table.RData (r-package equivalent) there's no typo (can't paste link because it is a binary), but I haven't compared the entire file. Is there a way to ensure both are equal?
Hi @vss-2 . Thanks for the heads up. Indeed, there is a typo in the `grid_state_correspondence_table.csv` in the Python package. Are you planning to create a pull request to fix this typo?  Or are you planning to create a pull request to add the `read_statistical_grid()` function to the Python package?
Thanks for the suggestion, I'm going to create two pull requests: 
* the first will fix the typos in `grid_state_correspondence_table.csv`
* the following one will add the `read_statistical_grid()` function
<br>

In addiction to my previous comment, after running `print(grid_state_correspondence_table)` in R,
```
             name_state abbrev_state code_grid
1                  Acre           AC     ID_50
...
```
I've noticed that column names are different in Python `grid_state_correspondence_table.csv`
```
name_uf,code_state,code_grid
Acre,AC,ID_50
...
```
As it seems to be the current project pattern, I will also modify the columns names to:
```
name_uf -> name_state
code_state -> abbrev_state
```
Hello, after running `make prepare-push ./Makefile`, following the contributor tutorial https://github.com/ipeaGIT/geobr/blob/c9787a742b099373c10939173995a974e187f62c/python-package/README.md?plain=1#L39
My commit was flooded with 60 indentation fixes by python's black package.
Since I had not change anything on these files, am I allowed to ignore the fixes and commit only the function implementation and test? 
![image](https://github.com/ipeaGIT/geobr/assets/32624576/bca0519e-1775-4ddc-b572-f6ee0b912243)

@JoaoCarabetta , what do you think?

Fix patch:
diff --git a/python-package/README.md b/python-package/README.md
index 5515699a..dc832fae 100644
--- a/python-package/README.md
+++ b/python-package/README.md
@@ -63,9 +63,9 @@ It adds:
 | read_region               | Yes         | Super |
 | read_semiarid             | Yes         | Super |
 | read_state                | Yes         | Super |
-| read_statistical_grid     | No          | No    |
+| read_statistical_grid     | Yes         | No    |
 | read_urban_area           | Yes         | Super |
-| read_urban_concentrations | No          | Super |
+| read_urban_concentrations | Yes         | Super |
 | read_weighting_area       | Yes         | No    |
 | list_geobr                | Yes         | Yes   |
 | lookup_muni               | Yes         | No    |
diff --git a/python-package/geobr/__init__.py b/python-package/geobr/__init__.py
index e70d31e2..c2af2dae 100644
--- a/python-package/geobr/__init__.py
+++ b/python-package/geobr/__init__.py
@@ -26,4 +26,5 @@
 from .read_urban_concentrations import read_urban_concentrations
 from .read_schools import read_schools
 from .read_comparable_areas import read_comparable_areas
-from .read_biomes import read_biomes
\ No newline at end of file
+from .read_biomes import read_biomes
+from .read_statistical_grid import read_statistical_grid
diff --git a/python-package/geobr/read_statistical_grid.py b/python-package/geobr/read_statistical_grid.py
new file mode 100644
index 00000000..cae3483a
--- /dev/null
+++ b/python-package/geobr/read_statistical_grid.py
@@ -0,0 +1,127 @@
+import sys
+from geobr import __path__ as geobr_directory
+from geobr.utils import select_metadata, download_gpkg
+from numpy import unique
+from pandas import read_csv
+
+
+def read_statistical_grid(code_grid="all", year=2010, simplified=False, verbose=False):
+    r"""Download spatial data of IBGE's statistical grid
+
+        @description
+    Data at scale 1:250,000, using Geodetic reference system "SIRGAS2000" and CRS(4674)
+
+       Parameters
+       ----------
+       code_grid:
+           If two-letter abbreviation or two-digit code of a state is
+           passed, the function will load all grid quadrants that
+           intersect with that state. If `code_grid="all"`, the grid of
+           the whole country will be loaded. Users may also pass a
+           grid quadrant id to load an specific quadrant. Quadrant ids
+           can be consulted at `grid_state_correspondence_table.csv`.
+       year : int, optional
+           Year of the data, by default 2010
+       simplified: boolean, by default False
+           Data 'type', indicating whether the function returns the 'original' dataset
+           with high resolution or a dataset with 'simplified' borders (Default)
+       verbose : bool, optional
+           by default False
+
+       Returns
+       -------
+       gpd.GeoDataFrame
+           Metadata and geopackage of selected states
+
+       Raises
+       ------
+       Exception
+           If parameters are not found or not well defined
+
+       Example
+       -------
+       >>> from geobr import read_statistical_grid
+
+       # Read specific state at a given year
+       >>> df = read_statistical_grid(year=2010)
+    """
+
+    temp_meta = select_metadata(
+        geo="statistical_grid", year=year, simplified=simplified
+    )
+
+    if temp_meta is None:
+        return None
+    
+    if(len(geobr_directory) == 0):
+        sys.exit("Geobr installation directory not found.")
+
+    grid_state_correspondence_table = None
+    grid_file_path = geobr_directory[0] + "/data/grid_state_correspondence_table.csv"
+
+    with open(grid_file_path, "rb") as file:
+        dtypes = {"name_state": str, "abbrev_state": str, "code_grid": str}
+        grid_state_correspondence_table = read_csv(
+            file, encoding="latin-1", dtype=dtypes
+        )
+
+    # Test if code_grid input is null
+    if code_grid == None:
+        sys.exit("Value to argument 'code_grid' cannot be NULL")
+
+    # if code_grid=="all", read the entire country
+    if code_grid == "all":
+        if verbose:
+            print("Loading data for the whole country. This might take a few minutes.")
+
+        file_url = temp_meta["download_path"]
+        temp_gpd = download_gpkg(file_url)
+
+        return temp_gpd
+
+    # Select abbrev_state column
+    grid_abbrev_state = grid_state_correspondence_table["abbrev_state"]
+
+    # Error if the input does not match any state abbreviation
+    if isinstance(code_grid, str) and not (code_grid in grid_abbrev_state.to_list()):
+        sys.exit(
+            "Error: Invalid Value to argument 'code_grid'. It must be one of the following: "
+            + str(unique(grid_abbrev_state.to_numpy().tolist()))
+        )
+
+    # Valid state abbreviation
+    elif isinstance(code_grid, str) and code_grid in grid_abbrev_state.to_list():
+        # Find grid quadrants that intersect with the passed state abbreviation
+        grid_state_correspondence_table_tmp = grid_state_correspondence_table[
+            grid_state_correspondence_table["abbrev_state"] == code_grid
+        ]
+
+        # Strips 'ID_' from code_grid string and gets only the int code value
+        grid_ids = [
+            substr[substr.index("_") + 1 :]
+            for substr in grid_state_correspondence_table_tmp["code_grid"].to_list()
+        ]
+
+        file_url = temp_meta[temp_meta["code"].isin(grid_ids)]
+        temp_gpd = download_gpkg(file_url)
+
+        return temp_gpd
+
+    # If code_grid is int
+    if isinstance(code_grid, int):
+        # Converts to str to match the following queries
+        code_grid = str(code_grid)
+
+        # Single digit case: adds a leading 0 (ex: 4 -> 04)
+        if len(code_grid) == 1:
+            code_grid = "0" + code_grid
+
+    if not (code_grid in temp_meta["code"].to_list()):
+        sys.exit("Error: Invalid Value to argument code_grid.")
+
+    else:
+        # Filters by code then download a list of gpkg filtered paths
+        file_url = temp_meta[temp_meta["code"].isin([code_grid])]
+        temp_gpd = download_gpkg(file_url)
+
+        return temp_gpd


Test patch:
diff --git a/python-package/tests/test_read_statistical_grid.py b/python-package/tests/test_read_statistical_grid.py
new file mode 100644
index 00000000..6bdafbf3
--- /dev/null
+++ b/python-package/tests/test_read_statistical_grid.py
@@ -0,0 +1,25 @@
+import geopandas as gpd
+import pytest
+from geobr import read_statistical_grid
+
+
+def test_read_statistical_grid():
+    assert isinstance(read_statistical_grid(code_grid=4), gpd.geodataframe.GeoDataFrame)
+
+    assert isinstance(
+        read_statistical_grid(code_grid="AC", year=2010), gpd.geodataframe.GeoDataFrame
+    )
+
+    # Too heavy
+    # assert isinstance(
+    #     read_statistical_grid(code_grid="all"),
+    #     gpd.geodataframe.GeoDataFrame
+    # )
+
+    with pytest.raises(Exception):
+        read_statistical_grid(year=9999999)
+
+        # Existing year but it has no code_grid specified
+        read_statistical_grid(year=2010)
+
+        read_statistical_grid(code_grid="AM_ACAP")