diff --git a/.gitignore b/.gitignore
index c6487d1..1f6a57a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,5 @@
demo.py
dist/
-
equi7grid_lite/__pycache__/
+docs/diagrams.drawio
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 59607b9..af19a9b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,7 +21,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
Unreleased changes here.
-## [0.1.1] - 2024-06-09
+## [0.3.0] - 2024-06-11
+
+- A new attribute added self.levels to the Equi7Grid constructor. This attribute is a List of the available levels in the QuadTree structure.
+- New arguments added to lonlat2grid and grid2lonlat methods. First a centroid argument. This argument is used to specify if user wants the centroid of the grid or the bottom-left corner. By default, the centroid is True in order to keep compatibility with the cubo package. The level argument is used to specify the level of the QuadTree structure reference. By default, the level is 0.
+- New method added to the Equi7Grid class. The method is called align2grid. This method is used to align coordinates to the QuadTree structure.
+
+## [0.2.0] - 2024-06-10
- Documentation in README.md and code comments improved.
- Shameful bug respect to the initial level. It must be 0 no 1. I'm not in R!
diff --git a/README.md b/README.md
index 09c8877..e768882 100644
--- a/README.md
+++ b/README.md
@@ -71,22 +71,30 @@ grid_system = Equi7Grid(min_grid_size=2560)
# zones: AN, NA, OC, SA, AF, EU, AS
# min_grid_size: 2560 meters
# max_grid_size: 1310720 meters
+```
+To convert between geographic coordinates and Equi7Grid tiles, use the `lonlat2grid` and `grid2lonlat` methods.
-# Convert geographic coordinates to Equi7Grid tile
+```python
lon, lat = -79.5, -5.49
grid_system.lonlat2grid(lon=lon, lat=lat)
# id x y zone level geometry
# 0 SA2560_E2008N2524 5140480.0 6461440.0 SA Z1 POLYGON ((5145600.000 6461440.000, 5145600.000...
-# Convert Equi7Grid tile to geographic coordinates
-grid_system.grid2lonlat(grid_id="SA2560_E2008N2524)
+grid_system.grid2lonlat(grid_id="SA2560_E2008N2524")
# lon lat x y
# 0 -79.543717 -5.517556 5140480 6461440
```
-The `Equi7Grid` class also provides a method for creating a grid of Equi7Grid tiles that cover a given bounding box.
+For users who want to align coordinates to the Equi7Grid Quad-Tree structure, the `align2grid` method is available.
+```python
+grid_system.align2grid(lon=lon, lat=lat, level=5)
+# lon lat
+#0 -80.116158 -6.105519
+```
+
+The `Equi7Grid` class also provides a method for creating a grid of Equi7Grid tiles that cover a given bounding box.
```python
import geopandas as gpd
diff --git a/docs/.$diagrams.drawio.bkp b/docs/.$diagrams.drawio.bkp
deleted file mode 100644
index 7eee619..0000000
--- a/docs/.$diagrams.drawio.bkp
+++ /dev/null
@@ -1,130 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/equi7grid_lite/main.py b/equi7grid_lite/main.py
index ab4cb78..c3c0fbe 100644
--- a/equi7grid_lite/main.py
+++ b/equi7grid_lite/main.py
@@ -1,6 +1,6 @@
import math
import re
-from typing import Dict, List, Literal, Optional, Tuple
+from typing import Dict, List, Literal, Optional, Tuple, Union
import geopandas as gpd
import numpy as np
@@ -67,6 +67,11 @@ def __init__(self, min_grid_size: int = 2560):
# Define the upper bounds (maximum grid size per zone)
self.zone_geometry_max_grids, self.max_grid_size = self.create_init_grid()
+ # Calculate the levels
+ max_level: int = int(np.log2(self.max_grid_size / self.min_grid_size))
+ self.levels = list(range(max_level))
+
+
# Define the Equi7Grid zones metadata
self.AN: Equi7GridZone = dict_to_datamodel(zone_metadata, 0)
self.NA: Equi7GridZone = dict_to_datamodel(zone_metadata, 1)
@@ -152,7 +157,7 @@ def create_grid(
level: int,
zone: Literal["AN", "NA", "OC", "SA", "AF", "EU", "AS"],
mask: Optional[shapely.geometry.base.BaseGeometry] = None,
- coverland: Optional[bool] = True,
+ coverland: Optional[bool] = True
) -> gpd.GeoDataFrame:
"""Create a grid for a specific zone.
@@ -253,12 +258,21 @@ def create_grid(
# intersect the grid with the zone geometry
return local_grid[local_grid.intersects(zone_geometry)]
- def lonlat2grid(self, lon: float, lat: float) -> gpd.GeoDataFrame:
+ def lonlat2grid(
+ self,
+ lon: float,
+ lat: float,
+ level: Optional[int] = 0,
+ centroid: Optional[bool] = True
+ ) -> gpd.GeoDataFrame:
"""Convert a latitude and longitude to an Equi7Grid Tile.
Args:
lon (float): The longitude.
lat (float): The latitude.
+ level (Optional[int], optional): The grid level. Defaults to 0.
+ centroid (Optional[bool], optional): If True, it will return the
+ centroid of the grid tile. Defaults to True.
Returns:
gpd.GeoDataFrame: The Equi7Grid Tile.
@@ -268,7 +282,7 @@ def lonlat2grid(self, lon: float, lat: float) -> gpd.GeoDataFrame:
point = gpd.GeoDataFrame(geometry=[Point(lon, lat)], crs="EPSG:4326")
# Find the zone where this point is located
- haversine_distance_min = 6378100
+ haversine_distance_min = math.inf
for index, zone in enumerate(self.zone_names):
# Load the Zone
zone_geom = self.zone_geometry[index]
@@ -284,7 +298,8 @@ def lonlat2grid(self, lon: float, lat: float) -> gpd.GeoDataFrame:
if condition:
lon_ref, lat_ref = self.zone_origin[index]
arc_distance = haversine_distance(lon, lat, lon_ref, lat_ref)
- #print("The zone is: ", zone, "The distance is: ", arc_distance)
+
+ # Update the minimum distance and set the best index (best zone)
if arc_distance < haversine_distance_min:
haversine_distance_min = arc_distance
best_index = index
@@ -293,27 +308,41 @@ def lonlat2grid(self, lon: float, lat: float) -> gpd.GeoDataFrame:
name = self.zone_names[best_index]
# Search in the level 1 grid & add bottom left coordinates
- q = self.create_grid(level=0, zone=name, mask=point.geometry[0], coverland=False)
- q.insert(1, "x", q.geometry.bounds.minx)
- q.insert(2, "y", q.geometry.bounds.miny)
+ q = self.create_grid(level=level, zone=name, mask=point.geometry[0], coverland=False)
+
+ if centroid:
+ q.insert(1, "x", q.geometry.centroid.x)
+ q.insert(2, "y", q.geometry.centroid.y)
+ else:
+ q.insert(1, "x", q.geometry.bounds.minx)
+ q.insert(2, "y", q.geometry.bounds.miny)
return q
def grid2lonlat(
- self, grid_id: str, xy_coords: Optional[bool] = True
+ self,
+ grid_id: Union[str, gpd.GeoDataFrame],
+ xy_coords: Optional[bool] = False,
+ centroid: Optional[bool] = True
) -> pd.DataFrame:
"""Convert an Equi7Grid Tile to a latitude and longitude.
Args:
grid_id (str): The Equi7Grid Tile ID.
xy_coords (Optional[bool], optional): If True, it will
- also return the X and Y coordinates. Defaults to
+ also return the X and Y coordinates in the Equi7Grid
+ system. Defaults to True.
+ centroid (Optional[bool], optional): If True, it will
+ return the centroid of the grid tile. Defaults to
True.
Returns:
pd.DataFrame: A DataFrame with the latitude and longitude
"""
-
+ # Check if the grid_id is a string or a GeoDataFrame
+ if isinstance(grid_id, gpd.GeoDataFrame):
+ grid_id = grid_id.id.values[0]
+
## Extract the zone
zone = grid_id[:2]
@@ -324,8 +353,13 @@ def grid2lonlat(
nytile = int(re_expr.search(grid_id).group(3))
# From Grid to Equi7Grid coordinates
- x = nxtile * distance
- y = nytile * distance
+ if centroid:
+ x = (nxtile + 0.5) * distance
+ y = (nytile + 0.5) * distance
+ else:
+ x = nxtile * distance
+ y = nytile * distance
+
point_local = Point(x, y)
# From Equi7Grid to Geographic coordinates
@@ -342,25 +376,50 @@ def grid2lonlat(
# from dict to dataframe
return pd.DataFrame(results, index=[0])
+ def align2grid(
+ self,
+ lon: float,
+ lat: float,
+ level: Optional[int] = 0,
+ centroid: Optional[bool] = True
+ ) -> gpd.GeoDataFrame:
+ """ Align the grid to the nearest grid tile.
+
+ Args:
+ lon (float): The longitude.
+ lat (float): The latitude.
+ level (int): The grid level. Defaults to 0.
+ centroid (Optional[bool], optional): If True, it will
+ return the centroid of the grid tile. Defaults to
+ True.
+
+ Returns:
+ gpd.GeoDataFrame: A GeoDataFrame with the coordinates
+ aligned to the grid.
+ """
+ return self.grid2lonlat(
+ grid_id=self.lonlat2grid(lon, lat, level=level, centroid=centroid),
+ centroid=centroid,
+ xy_coords=False
+ )
+
+
def __str__(self) -> str:
"""Display the Equi7Grid information.
Returns:
str: A string representation of the Equi7Grid information.
"""
- # Calculate the levels
- levels = int(np.log2(self.max_grid_size / self.min_grid_size))
- levels = list(range(levels))
# If levels has more than 4 elements
- if len(levels) > 4:
- levels0 = levels[0]
- levels1 = levels[1]
- levelsn = levels[-1]
- levelsn1 = levels[-2]
+ if len(self.levels) > 4:
+ levels0 = self.levels[0]
+ levels1 = self.levels[1]
+ levelsn = self.levels[-1]
+ levelsn1 = self.levels[-2]
level_msg = f"{levels0}, {levels1}, ... , {levelsn1}, {levelsn}"
else:
- level_msg = f"{', '.join(map(str, levels))}"
+ level_msg = f"{', '.join(map(str, self.levels))}"
message = f"Equi7Grid(min_grid_size={self.min_grid_size})\n"
message += f"----------------\n"
diff --git a/pyproject.toml b/pyproject.toml
index 213471d..5a82ff4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "equi7grid_lite"
-version = "0.2.0"
+version = "0.3.0"
description = "A user-friendly Python interface to interact with the Equi7Grid grid system"
authors = ["Cesar Aybar ", "Jair Flores "]
repository = "https://github.com/csaybar/equi7grid-lite"