Skip to content

Commit

Permalink
feat: Added new functions and updated README
Browse files Browse the repository at this point in the history
- Added locate function from coordinates
- Added get_metadata function
- Improved search function
- Improved code documentation
- Updated README
  • Loading branch information
JCruiz15 authored and jfaldanam committed Jun 25, 2024
1 parent 4c43b71 commit 0ba79bc
Show file tree
Hide file tree
Showing 5 changed files with 452 additions and 19 deletions.
113 changes: 111 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,111 @@
# SIGPAC-Tools
SIGPAC-Tools is a custom Python library that provides the user a comfortable client to retrieve data from SIGPAC api, saving the user from downloading and storing heavy files.
# [SIGPAC-Tools](https://github.com/KhaosResearch/sigpac-tools)

![Python >=3.10](https://img.shields.io/badge/python-%3E=3.10-blue.svg)
![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)
![Status: Active](https://img.shields.io/badge/Status-Active-00aa00.svg)
![Code style: Ruff](https://img.shields.io/badge/code%20style-Ruff-aa0000.svg)


SIGPAC-Tools is a Python library that provides the user a set of tools to work with the SIGPAC data. The library is designed to be easy to use and to provide the user the ability to work with the data in a simple way.

This library is designed to be used in conjunction with the SIGPAC data provided by the Spanish government. The data is available in the [SIGPAC website](https://sigpac.mapa.gob.es/fega/visor/).

## Installation

To install the library, you can use the following command:

```bash
git clone https://github.com/KhaosResearch/sigpac-tools
python -m pip install ./sigpac-tools
```

or you can install it directly from the repository:

```bash
python -m pip install https://github.com/KhaosResearch/sigpac-tools
```

## Usage

Once the library is installed, you can use it in your Python code. The library provides a set of tools to work with the SIGPAC data, these are the main features:

### Search for an specific plot

You can search for an specific plot using the `search` function in the `search` module. This function will return a list of plots that match the search criteria.

```python
from sigpac_tools.search import search

data = {
"community": 1,
"province": 1,
"municipality": 8,
"polygon": 8,
"parcel": 572
}

geojson = search(data)
```

It is important to note that the `search` function will return a GeoJSON object with the information of the plots that match the search criteria. The `data` dictionary must contain at least the 'province' or the 'community' key to return a valid result. Note that all the keys must be in lower case.

### Get the information of a plot

You can get the information of a plot using the `get_metadata` function in the `anotate` module. This function will return a dictionary with the information of the plot, given the same data as the `search` function.

```python
from sigpac_tools.anotate import get_metadata

layer = "parcela"
data = {
"community": 1,
"province": 1,
"municipality": 8,
"polygon": 8,
"parcel": 572,
# "enclosure": 1,
# "aggregate": 0,
# "zone": 0
}

metadata = get_metadata(
layer,
data
)
```

> `enclosure` is only necesary if the layer is "recinto". `aggregate` and `zone` are optional and depend on the location of the parcel or enclosure searched for.
### Get the geometry of a plot

Given the coordinates of the plot, that may be gathered from the function `search`, you can get the geometry of the plot using the `geometry_from_coords` function in the `locate` module. This function will return a GeoJSON object with the geometry of the plot.

The user may specify a reference parcel or enclosure to get the geometry of and so on the function will return the geometry of that specific parcel or enclosure if it exists. If the reference parcel or enclosure does not exist, the function will return `None`.

If the reference parcel or enclosure is not provided, the function will return a Feature Collection with all the parcels that match the search criteria.

```python
from sigpac_tools.locate import geometry_from_coords

layer = "parcela"
lat = 37.384
lng = -4.98
reference = None

geometry = geometry_from_coords(
layer,
lat,
lng,
reference
)
```

## Acknowledgements

This inspired by the JavaScript [SIGPAC client](https://github.com/dan96ct/sigpac-client) made by Daniel Cebrián.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

Copyright 2023 Khaos Research, all rights reserved.
145 changes: 145 additions & 0 deletions sigpac_tools/anotate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import requests
import structlog

from sigpac_tools._globals import BASE_URL

logger = structlog.get_logger()


def __query(
layer: str,
province: int,
municipality: int,
polygon: int,
parcel: int,
enclosure: int,
aggregate: int,
zone: int,
) -> dict:
"""Queries the SIGPAC database for the given location parameters and layer
Parameters
----------
layer : str
Layer to search from ("parcela", "recinto")
province : int
Province code
municipality : int
Municipality code
polygon : int
Polygon code
parcel : int
Parcel code
enclosure : int
Enclosure code
aggregate : int
Aggregate code
zone : int
Zone code
Returns
-------
dict
Dictionary with the metadata of the SIGPAC database
Raises
------
KeyError
If the layer is not supported
"""
match layer.lower():
case "parcela":
logger.info("Searching for the parcel specified")
url = f"{BASE_URL}/fega/ServiciosVisorSigpac/LayerInfo"
params = {
"layer": layer,
"id": f"{province},{municipality},{aggregate},{zone},{polygon},{parcel}",
}
response = requests.get(url, params=params)
return response.json()
case "recinto":
logger.info("Searching for the enclosure specified")
url = f"{BASE_URL}/fega/ServiciosVisorSigpac/LayerInfo"
params = {
"layer": layer,
"id": f"{province},{municipality},{aggregate},{zone},{polygon},{parcel},{enclosure}",
}
response = requests.get(url, params=params)
print(response.url)
return response.json()
case _:
logger.error(
"Layer not supported. Supported layers: ['parcela', 'recinto']"
)
raise KeyError(
"Layer not supported. Supported layers: ['parcela', 'recinto']"
)


def get_metadata(layer: str, data: dict):
"""Get the metadata of the given location from the SIGPAC database
It searches for the metadata of the given location in the SIGPAC database. The search can be done by specifying the layer, province, municipality, polygon and parcel.
The level of detail of the search can be increased by specifying the layer to search between "parcela" and "recinto".
Parameters
----------
layer : str
Layer to search from ("parcela", "recinto")
data : dict
Dictionary with the data of the location to search. It must be a dictionary with the following keys: [ province, municipality, aggregate, zone, polygon, parcel ]
Returns
-------
dict
Dictionary with the metadata of the SIGPAC database
Raises
------
ValueError
If the layer, province, municipality, polygon or parcel is not specified
KeyError
If the layer is not supported
"""
if not layer:
logger.error("Layer not specified")
raise ValueError("Layer not specified")
elif layer not in ["parcela", "recinto"]:
logger.error("Layer not supported. Supported layers: ['parcela', 'recinto']")
raise KeyError("Layer not supported. Supported layers: ['parcela', 'recinto']")

prov = data.get("province", None)
muni = data.get("municipality", None)
polg = data.get("polygon", None)
parc = data.get("parcel", None)
encl = data.get("enclosure", None)
aggr = data.get("aggregate", 0)
zone = data.get("zone", 0)

if not prov:
logger.error("Province not specified")
raise ValueError("Province not specified")
if not muni:
logger.error("Municipality not specified")
raise ValueError("Municipality not specified")
if not polg:
logger.error("Polygon not specified")
raise ValueError("Polygon not specified")
if not parc:
logger.error("Parcel not specified")
raise ValueError("Parcel not specified")
if not encl and layer == "recinto":
logger.error("Enclosure not specified")
raise ValueError("Enclosure not specified")

logger.info(f"Searching for the metadata of the location (province {prov}, municipality {muni}, polygon {polg}, parcel {parc}) in the SIGPAC database...")
return __query(
layer=layer,
province=prov,
municipality=muni,
polygon=polg,
parcel=parc,
enclosure=encl,
aggregate=aggr,
zone=zone,
)
90 changes: 90 additions & 0 deletions sigpac_tools/locate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import requests
import structlog

from sigpac_tools._globals import BASE_URL
from sigpac_tools.utils import lng_lat_to_tile, transform_coords

logger = structlog.get_logger()


def __locate_in_feature_collection(
reference: int, layer: str, featureCollection: dict
) -> dict | None:
"""Locates the given reference in the feature collection given the layer to search from
Parameters
----------
reference : int
Reference to search
layer : str
Layer to search from ("parcela", "recinto")
featureCollection : dict
Geojson feature collection to search from
Returns:
dict | None
Geojson geometry of the found reference. If not found, returns `None`
"""
for feature in featureCollection["features"]:
if feature["properties"][layer] == reference:
transform_coords(feature)
return feature["geometry"]
return None


def geometry_from_coords(layer: str, lat: float, lon: float, reference: int) -> dict:
"""Gets the geometry of the given coordinates and reference in the given layer
Parameters
----------
layer : str
Layer to search from ("parcela", "recinto")
lat : float
Latitude of the location
lon : float
Longitude of the location
reference : int
Reference to search for
Returns
-------
dict
Geojson geometry of the found reference
Raises
------
ValueError
If the layer, latitude, longitude or reference is not specified
KeyError
If the layer is not supported
"""
if not layer or not lat or not lon or not reference:
logger.error("Layer, latitude, longitude or reference not specified")
raise ValueError("Layer, latitude, longitude or reference not specified")

tile_x, tile_y = lng_lat_to_tile(lon, lat, 15)

response = requests.get(
f"{BASE_URL}/vectorsdg/vector/{layer}@3857/15.{tile_x}.{tile_y}.geojson"
)

geojson_features = response.json()

if layer in ["parcela", "recinto"]:
logger.info(f"Searching for reference {reference} in the layer {layer}...")
result = __locate_in_feature_collection(
reference=reference, layer=layer, featureCollection=geojson_features
)
if not result:
logger.warning(
f"Reference '{reference}' not found in the layer '{layer}' at coordinates ({lat}, {lon})"
)
return result

else:
logger.error(
f'Layer "{layer}" not supported. Supported layers: "parcela", "recinto"'
)
raise KeyError(
f'Layer "{layer}" not supported. Supported layers: "parcela", "recinto"'
)
Loading

0 comments on commit 0ba79bc

Please sign in to comment.