Víboras (Género Porthidium)¶
La familia Viperidae incluye a serpientes muy eficientes para depredación, en particular, por su organo inoculador de veneno y a otras modificaciones fisiológicas que presentan sus cráneos,lo cual les permite cazar, someter y tragar presas relativamentes grandes (Savage, 2002)
Género Porthidium en Costa Rica¶
Dentro de la familia Viperidae, el grupo Porthidium comprende tres géneros: Atropoides, Cerrophidion y Porthidium, que se originaron y evolucionaron en Mesoamérica (Lamar & Sasa, 2003). El género Porthidium incluye 9 especies de hábitos terrestres que en su mayoría miden menos de 1 m de longitud, muestran patrones de colores crípticos y un cantus rostralis afilado que es más alto que ancho. Su hocico es generalmente atenuado y se puede elevar moderada o grandemente. Popularmente se conoce a estas serpientes como 'víboras nariz de cerdo', o 'hognose vipers' en la literatura en inglés. Son moderadamente fornidas y habitan bosques secos o de transición (Lamar & Sasa, 2003) de México, Centroamérica y el norte de Suramérica.
En Costa Rica habitan 4 especies de este género: Porthidium nasutum, P.ophryomegas, P. volcanicum y la recientemente descrita P. porrasi (Lamar & Sasa, 2003). Porthidium nasutum o 'tamagá común' es una serpiente con una longitud promedio de 50 cm en adultos. Las hembras son más largas y robustas que los machos. La cabeza es grande, ancha y bien definida. El hocico es puntiagudo y la cola corta. La escama rostral, en la punta del hocico, es notablemente alta, lo que le confiere un aspecto de cuerno o proboscis. Esta es la característica diagnóstica de la especie en Costa Rica. Las escamas dorsales son quilladas y el color de fondo muy variable: desde gris, rojizo hasta café oscuro y casi negro. Presenta, además, una serie de marcas rectangulares oscuras a lo largo del cuerpo, separadas por una línea vertebral angosta. Presenta 8-11 supralabiales, 9-13 infralabiales, 4-7 interoculares, 22-25 hileras de escamas dorsales a la mitad del cuerpo, 128-144 ventrales, escama anal entera y 23-41 subcaudales enteras. La tamagá es una especie terrestre común en la hojarasca del bosque, donde es activa tanto de día como de noche. Caza principalmente ranas y lagartijas de hojarasca. Se expande desde México hasta el noroeste de Ecuador. En Costa Rica se le encuentra en bosques lluviosos tropicales y subtropicales de la vertiente Caribe, desde el nivel del mar a los 1 500 m de elevación (Solórzano, 2004).
Carga de datos de ocurrencias de Porthidium sp.¶
Registro de avistamientos de las diferentes especies del género Porthidium¶
# Instalación de pygbif
!pip install pygbif --quiet
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 70.2/70.2 kB 2.0 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 61.4/61.4 kB 2.5 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 66.4/66.4 kB 2.9 MB/s eta 0:00:00
from pygbif import occurrences
import pandas as pd # biblioteca para análisis de datos
# Nombre científico de la especie a buscar, debe coincidir con el nombre registro en Gbif
especie = "Porthidium"
limite = 250 # Límite de registros por solicitud
offset = 0 # Desplazamiento para iniciar la solicitud
registros_acumulados = [] # Lista para acumular los resultados
# Ciclo que obtiene los registros en la cantidad especificada
# en la variable limite
while True:
# Solicitud, en esta sección se delimita los párametros de busqueda.
res = occurrences.search(
scientificName=especie,
hasCoordinate=True,
hasGeospatialIssue=False,
limit=limite,
offset=offset
)
# Extraer resultados
registros = res.get("results", [])
# Si ya no hay resultados, se detiene el ciclo
if not registros:
break
# Agregar registros nuevos al acumulado
registros_acumulados.extend(registros)
# Actualizar el offset para la siguiente "página"
offset += limite
# Convertir todo a un DataFrame de pandas
presencia = pd.DataFrame(registros_acumulados)
# Cantidad de registros de presencia recuperados
print(f"Se recuperaron {len(presencia)} registros de presencia")
# Muestra de los registros recuperados, seleccionando solo las columnas presentes
columnas_disponibles = presencia.columns.intersection(['species', 'basisOfRecord', 'countryCode', 'locality', 'decimalLongitude', 'decimalLatitude', 'eventDate', 'year', 'recordedBy'])
# Get the minimum between the desired sample size and the actual number of rows.
sample_size = min(10, len(presencia[columnas_disponibles]))
# Sample the DataFrame with the adjusted sample size or with replacement if the sample size is 0.
if sample_size > 0:
presencia[columnas_disponibles].sample(sample_size)
else:
# If there are no rows to sample, you can display a message or handle it differently.
print("No rows to sample.")
Se recuperaron 2919 registros de presencia
Número de avistamientos agrupados por país¶
En el gráfico siguiente se puede observar los avistamientos reportados del género Porthidium para el continente americano, donde destacan Colombia, Costa Rica y México con mayor cantidad.
# Importación de matplotlib
import matplotlib.pyplot as plt
# 1. Contar registros por país
registros_x_pais = presencia['country'].value_counts()
# 2. Generar un gráfico de barras
plt.figure(figsize=(10, 6)) # tamaño del gráfico
registros_x_pais.plot(kind='bar')
# 3. Especificar el título y las etiquetas del gráfico
plt.title('Cantidad de registros por país')
plt.xlabel('País')
plt.ylabel('Cantidad de registros')
# 4. Mostrar el gráfico
plt.show()
# Carga de geopandas con el alias gdp
import geopandas as gpd
# Carga de pandas con el alias pd
import pandas as pd
# Carga de folium
import folium
# Carga de matplotlib
import matplotlib.pyplot as plt
# Biblioteca requerida para mapas interactivos
!pip install mapclassify --quiet
import mapclassify
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/59.1 kB ? eta -:--:-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 59.1/59.1 kB 2.5 MB/s eta 0:00:00
# Montar unidad drive para cargar base de datos de registros en Costa Rica
from google.colab import drive
drive.mount('/content/drive')
Mounted at /content/drive
# Crear un DataFrame con registros de presencia de porthidium
porthidium_df = pd.read_csv(
'//content/drive/MyDrive/Colab Notebooks/Datos proyecto final/2025/Porthidium sp_CR.csv',
sep='\t',
)
# Crear un GeoDataFrame a partir del DataFrame
porthidium_gdf = gpd.GeoDataFrame(
porthidium_df,
geometry=gpd.points_from_xy(porthidium_df.decimalLongitude, porthidium_df.decimalLatitude),
crs='EPSG:4326'
)
# Mostrar los primeros registros del GeoDataFrame,
# incluyendo la columna de geometría
porthidium_gdf[['gbifID', 'species', 'decimalLongitude', 'decimalLatitude', 'geometry']].head(20)
/usr/local/lib/python3.11/dist-packages/geopandas/geoseries.py:860: UserWarning: GeoSeries.notna() previously returned False for both missing (None) and empty geometries. Now, it only returns False for missing values. Since the calling GeoSeries contains empty geometries, the result has changed compared to previous versions of GeoPandas. Given a GeoSeries 's', you can use '~s.is_empty & s.notna()' to get back the old behaviour. To further ignore this warning, you can do: import warnings; warnings.filterwarnings('ignore', 'GeoSeries.notna', UserWarning) return self.notna()
gbifID | species | decimalLongitude | decimalLatitude | geometry | |
---|---|---|---|---|---|
0 | 991955938 | Porthidium nasutum | -84.689033 | 10.503133 | POINT (-84.68903 10.50313) |
1 | 991955937 | Porthidium nasutum | -84.689033 | 10.503133 | POINT (-84.68903 10.50313) |
2 | 991955936 | Porthidium nasutum | -84.689033 | 10.503133 | POINT (-84.68903 10.50313) |
3 | 991955935 | Porthidium nasutum | -84.689033 | 10.503133 | POINT (-84.68903 10.50313) |
4 | 991955934 | Porthidium nasutum | -84.689033 | 10.503133 | POINT (-84.68903 10.50313) |
5 | 991955933 | Porthidium nasutum | -84.689033 | 10.503133 | POINT (-84.68903 10.50313) |
6 | 991955931 | Porthidium nasutum | -84.689033 | 10.503133 | POINT (-84.68903 10.50313) |
7 | 991955930 | Porthidium ophryomegas | -85.349717 | 10.396117 | POINT (-85.34972 10.39612) |
8 | 991955929 | Porthidium nasutum | -84.689033 | 10.503133 | POINT (-84.68903 10.50313) |
9 | 891071760 | Porthidium nasutum | -84.068100 | 10.450527 | POINT (-84.0681 10.45053) |
10 | 786437924 | NaN | -85.000000 | 10.480000 | POINT (-85 10.48) |
11 | 786435514 | NaN | -84.078056 | 10.258561 | POINT (-84.07806 10.25856) |
12 | 786299434 | NaN | -85.068053 | 10.458553 | POINT (-85.06805 10.45855) |
13 | 736313846 | Porthidium ophryomegas | NaN | NaN | POINT EMPTY |
14 | 686908718 | Porthidium ophryomegas | NaN | NaN | POINT EMPTY |
15 | 686908717 | NaN | -83.683300 | 9.900000 | POINT (-83.6833 9.9) |
16 | 686846956 | Porthidium ophryomegas | -85.536381 | 10.790203 | POINT (-85.53638 10.7902) |
17 | 686846955 | Porthidium ophryomegas | -85.523581 | 10.759405 | POINT (-85.52358 10.75941) |
18 | 686846954 | Porthidium nasutum | -83.616670 | 9.950000 | POINT (-83.61667 9.95) |
19 | 686742347 | Porthidium nasutum | -84.950603 | 10.505779 | POINT (-84.9506 10.50578) |
# Crear un geodataframe con datos y polígonos de provincias
provincias_gdf = gpd.read_file(
'/content/drive/MyDrive/Colab Notebooks/Datos Tarea 3/Provincias.gpkg'
)
# Mostrar los primeros registros del GeoDataFrame,
# incluyendo la columna de geometría
provincias_gdf[['PROVINCIA', 'AREA', 'geometry']].head(7)
Avistamientos reportados para Costa Rica¶
En el mapa siguiente se pueden visualizar la distribución de los reportes para el género Porthidium en las provincias de Costa Rica.
# Mapa estático de registros de porthidium
porthidium_gdf.plot()
provincias_gdf.plot()
<Axes: >
Muestra de ocurrencias por provincias en Costa Rica
# Crear figura y ejes
fig, ax = plt.subplots(figsize=(8, 6))
# Capa de provincias de Costa Rica
provincias_gdf[(provincias_gdf['PROVINCIA'] == 'Guanacaste') | (provincias_gdf['PROVINCIA'] == 'Alajuela') | (provincias_gdf['PROVINCIA'] == 'Cartago') | (provincias_gdf['PROVINCIA'] == 'Heredia') | (provincias_gdf['PROVINCIA'] == 'Limón') | (provincias_gdf['PROVINCIA'] == 'Puntarenas') | (provincias_gdf['PROVINCIA'] == 'San José') ].plot(
ax=ax,
color="white",
edgecolor="black"
)
# Capa de registros de porthidium en Guanacaste y Alajuela
porthidium_gdf[(porthidium_gdf['decimalLongitude'] < 0) & (porthidium_gdf['decimalLatitude'] < 16)].plot(
ax=ax,
marker="o",
color="red",
markersize=5
)
<Axes: >
Mapa interactivo de avistamientos de porthidium en Costa Rica¶
# Mapa interactivo de registros de porthidium
porthidium_gdf[(porthidium_gdf['decimalLongitude'] < 0) & (porthidium_gdf['decimalLatitude'] < 16)].explore()
Riqueza de especies del género Porthidium en Costa Rica por provincias¶
# Filtrar los datos
porthidium_provincias_gdf = porthidium_gdf[(porthidium_gdf['decimalLongitude'] < 0) & (porthidium_gdf['decimalLatitude'] < 16)]
provincias_costarica_gdf = provincias_gdf[(provincias_gdf['PROVINCIA'] == 'Guanacaste') | (provincias_gdf['PROVINCIA'] == 'Alajuela')| (provincias_gdf['PROVINCIA'] == 'Cartago') | (provincias_gdf['PROVINCIA'] == 'Heredia') | (provincias_gdf['PROVINCIA'] == 'Limón') | (provincias_gdf['PROVINCIA'] == 'Puntarenas') | (provincias_gdf['PROVINCIA'] == 'San José') ]
# Definir un índice
provincias_costarica_gdf.set_index('PROVINCIA', inplace=True)
# Unión (join) espacial
porthidium_provincias_costarica_gdf = porthidium_provincias_gdf.sjoin(
provincias_gdf,
predicate='intersects'
)
porthidium_provincias_costarica_gdf[['gbifID', 'species', 'CÓDIGO_PR', 'PROVINCIA']].head(200)
gbifID | species | CÓDIGO_PR | PROVINCIA | |
---|---|---|---|---|
0 | 991955938 | Porthidium nasutum | 2 | Alajuela |
1 | 991955937 | Porthidium nasutum | 2 | Alajuela |
2 | 991955936 | Porthidium nasutum | 2 | Alajuela |
3 | 991955935 | Porthidium nasutum | 2 | Alajuela |
4 | 991955934 | Porthidium nasutum | 2 | Alajuela |
... | ... | ... | ... | ... |
258 | 476706248 | Porthidium nasutum | 3 | Cartago |
259 | 476706247 | Porthidium nasutum | 3 | Cartago |
260 | 476706246 | Porthidium nasutum | 3 | Cartago |
261 | 476706245 | Porthidium nasutum | 3 | Cartago |
262 | 476706141 | Porthidium nasutum | 3 | Cartago |
200 rows × 4 columns
Conteo de Porthidium sp por provincias en Costa Rica¶
# Conteo de especies en cada provincia
conteo_especies_por_provincia = porthidium_provincias_costarica_gdf.groupby("PROVINCIA").species.nunique()
# Convertir la serie a dataframe
conteo_especies_por_provincia = conteo_especies_por_provincia.reset_index()
# Definir un índice
conteo_especies_por_provincia.set_index('PROVINCIA', inplace= True)
# Cambio de nombre de columna
conteo_especies_por_provincia.rename(columns = {'species': 'especies_porthidium'}, inplace = True)
# Despliegue de países y cantidades de especies
conteo_especies_por_provincia.sort_values(by="especies_porthidium", ascending=False)
especies_porthidium | |
---|---|
PROVINCIA | |
Puntarenas | 4 |
Alajuela | 2 |
Guanacaste | 2 |
Limón | 2 |
Cartago | 1 |
Heredia | 1 |
San José | 1 |
# Join para agregar la columna con el conteo a la capa provincias
provincias_costarica_riqueza_porthidium_gdf = provincias_costarica_gdf.join(conteo_especies_por_provincia)
provincias_costarica_riqueza_porthidium_gdf[['especies_porthidium']].sort_values(by='especies_porthidium', ascending=False)
especies_porthidium | |
---|---|
PROVINCIA | |
Puntarenas | 4 |
Alajuela | 2 |
Guanacaste | 2 |
Limón | 2 |
Cartago | 1 |
Heredia | 1 |
San José | 1 |
Mapa de coropletas interactivo de las distintas especies del género Porthidium en Costa Rica¶
# Capa de provincias
m = provincias_gdf[(provincias_gdf['PROVINCIA'] == 'Guanacaste') | (provincias_gdf['PROVINCIA'] == 'Alajuela')| (provincias_gdf['PROVINCIA'] == 'Cartago') | (provincias_gdf['PROVINCIA'] == 'Heredia') | (provincias_gdf['PROVINCIA'] == 'Limón') | (provincias_gdf['PROVINCIA'] == 'Puntarenas') | (provincias_gdf['PROVINCIA'] == 'San José')].explore(
name='Provincias',
tooltip=['PROVINCIA'],
popup=True,
style_kwds={
'fillColor': 'lightgray',
'color': 'green',
'weight': 1,
'fillOpacity': 0.5
}
)
# Capa de porthidium
porthidium_gdf[(porthidium_gdf['decimalLongitude'] < 0) & (porthidium_gdf['decimalLatitude'] < 16)].explore(
m=m, # se usa el mapa que se creó en la instrucción anterior
name='Porthidium',
marker_type='circle',
marker_kwds={'radius': 20, 'color': 'red'},
tooltip=['species', 'locality', 'eventDate'],
popup=True
)
# Agregar un control de capas al mapa
folium.LayerControl().add_to(m)
# Definir colores manualmente para cada especie
colores_personalizados = {
"Porthidium nasutum": "#d7ca31", # Amarillo verdoso
"Porthidium ophryomegas": "#8a7154", # Café opaco
"Porthidium volcanicum": "#3357FF", # Azul fuerte
"Porthidium porrasi": "#F4A460", # Marrón claro
}
# Get unique species from the 'species' column of porthidium_gdf
especies_unicas = porthidium_gdf['species'].unique()
# Si una especie no tiene color definido, asignar un color gris por defecto
colores = {especie: colores_personalizados.get(especie, "#808080") for especie in especies_unicas}
# Agregar puntos al mapa, diferenciando cada especie por color
for _, row in porthidium_gdf.iterrows():
# Check if the species is in the colores dictionary and coordinates are valid
if row["species"] in colores and not pd.isna(row["decimalLatitude"]) and not pd.isna(row["decimalLongitude"]):
folium.CircleMarker(
location=[row["decimalLatitude"], row["decimalLongitude"]],
radius=1,
color=colores[row["species"]],
fill=True,
fill_color=colores[row["species"]],
fill_opacity=0.2,
popup=f"{row['species']}" # Muestra el nombre de la especie al hacer clic
).add_to(m)
else:
print(f"Warning: Species '{row['species']}' not found in color dictionary. Skipping.")
# Agregar un control de capas al mapa
folium.LayerControl().add_to(m)
# Mostrar el mapa interactivo
m
Biblioteca para archivos raster¶
# Instalación de rasterio
!pip install rasterio --quiet
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 22.2/22.2 MB 67.1 MB/s eta 0:00:00
Carga de datos
# Carga de rasterio
import rasterio
# Carga de rasterio.plot (para graficar datos raster)
import rasterio.plot
# Carga de numpy (para álgebra lineal)
import numpy as np
# Carga de matplotlib.pyplot
import matplotlib.pyplot as plt
Datos de temperatura media anual; consulta de propiedades y metadatos
# Carga de datos de temperatura media anual
temperatura_media_anual = rasterio.open(
'https://github.com/datos-geoespaciales-biodiversidad/python/raw/refs/heads/main/datos/clima/worldclim/2.1-10m-bio/wc2.1_10m_bio_1.tif'
)
print(temperatura_media_anual)
<open DatasetReader name='https://github.com/datos-geoespaciales-biodiversidad/python/raw/refs/heads/main/datos/clima/worldclim/2.1-10m-bio/wc2.1_10m_bio_1.tif' mode='r'>
# Nombre de la fuente de datos
temperatura_media_anual.name
# CRS
temperatura_media_anual.crs
# Resolución (tamaño de cada celda)
temperatura_media_anual.res
# Límites
temperatura_media_anual.bounds
# Todos los metadatos
temperatura_media_anual.meta
{'driver': 'GTiff', 'dtype': 'float32', 'nodata': -3.3999999521443642e+38, 'width': 2160, 'height': 1080, 'count': 1, 'crs': CRS.from_wkt('GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'), 'transform': Affine(0.16666666666666666, 0.0, -180.0, 0.0, -0.16666666666666666, 90.0)}
import rasterio
from rasterio import plot # Asegurarse de importar el submódulo plot de rasterio
Mapeo en imágenes satelitales para temperatura en el Mundo y Costa Rica¶
# Mapa de temperatura media anual
rasterio.plot.show(temperatura_media_anual)
fig, ax = plt.subplots(figsize=(8, 8))
# Mapa de temperatura media anual
rasterio.plot.show(
temperatura_media_anual,
cmap="coolwarm", # colores
ax=ax,
title="Temperatura media anual Costa Rica"
)
# Agregar una leyenda
cbar = fig.colorbar(ax.images[0], ax=ax, shrink=0.8)
cbar.set_label('°C')
# Limitar el rango de los ejes
ax.set_xlim(-88.0, -80.0)
ax.set_ylim(5.0, 12.0)
plt.show()
Imagen satelital para ánalisis de vegetación de una sección del área de distribución en Guanacaste para la especie P. Ophryomegas¶
# Lectura de imagen Sentinel
import rasterio # Importación del módulo rasterio
imagen = rasterio.open(
'/content/drive/MyDrive/Colab Notebooks/Datos proyecto final/2025/2025-03-03-00_00_2025-03-03-23_59_Sentinel-2_L1C_NDVI.tiff'
)
print(imagen)
<open DatasetReader name='/content/drive/MyDrive/Colab Notebooks/Datos proyecto final/2025/2025-03-03-00_00_2025-03-03-23_59_Sentinel-2_L1C_NDVI.tiff' mode='r'>
Metadatos
# Metadatos
imagen.meta
{'driver': 'GTiff', 'dtype': 'float32', 'nodata': None, 'width': 2398, 'height': 1546, 'count': 3, 'crs': CRS.from_wkt('GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'), 'transform': Affine(8.99826317056265e-05, 0.0, -85.78186798724347, 0.0, -8.995195923314384e-05, 10.969215705051996)}
# Lista de nombres de bandas
nombres_bandas = [
"aerosol de costa",
"azul",
"verde",
"roja",
"NIR",
"SWIR1",
"SWIR2"
]
Visualización de imagen raster en una sección del hábitat de la zona de distribución en la provincia de Guanacaste de *P. ophryomegas texto en negrita texto en negrita¶
# Import necessary modules
import rasterio
import rasterio.plot # Import the plot submodule
# Visualización de una banda
rasterio.plot.show((imagen, 1), cmap="Greys_r")
<Axes: >
fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(8, 10))
axes = axes.flatten()
for banda in range(1, imagen.count + 1):
datos = imagen.read(banda)
ax = axes[banda - 1]
im = ax.imshow(datos, cmap="BrBG_r")
ax.set_title(f"Banda {nombres_bandas[banda - 1]}")
fig.colorbar(im, ax=ax, label="Reflectancia", shrink=0.8)
plt.tight_layout()
plt.show()
Cálculo del NDVI para un sitio del Sector Murciélago del ACG¶
import rasterio
import rasterio.plot
import numpy as np # Import numpy
import matplotlib.pyplot as plt
red_band = imagen.read(1).astype('float32')
# The raster only has one band, so we'll use that for green and nir as well
green_band = imagen.read(1).astype('float32') # Using band 1 for green
nir_band = imagen.read(2).astype('float32') # Using band 1 for nir
# Manejar valores NaN
nir_band = np.nan_to_num(nir_band, nan=nir_band.min())
red_band = np.nan_to_num(red_band, nan=red_band.min())
green_band = np.nan_to_num(green_band, nan=green_band.min())
# Normalizar las bandas
nir_norm = (nir_band - nir_band.min()) / (nir_band.max() - nir_band.min())
red_norm = (red_band - red_band.min()) / (red_band.max() - red_band.min())
green_norm = (green_band - green_band.min()) / (green_band.max() - green_band.min())
# Verificar que los valores están entre 0 y 1
nir_norm = nir_norm.clip(0, 1)
red_norm = red_norm.clip(0, 1)
green_norm = green_norm.clip(0, 1)
# Arreglo de bandas
rgb = np.dstack((red_norm, green_norm, nir_norm))
# Cálculo del NDVI: NDVI = (NIR - roja) / (NIR + roja)
ndvi = (nir_band - red_band) / (nir_band + red_band)
ndvi = ndvi.clip(-1, 1)
plt.figure(figsize=(8, 8))
plt.imshow(ndvi, cmap="RdYlGn", vmin=-1, vmax=1)
plt.colorbar(label="NDVI", shrink=0.5)
plt.title("NDVI")
plt.show()
import numpy as np
import rasterio
from rasterio.transform import from_origin
import geopandas as gpd
from pygbif import occurrences
import pandas as pd
Mapa de las Áreas de Conservación de Costa Rica utilizando Leafmap¶
# Instalación de leafmap
!pip install leafmap --quiet
# Instalación de rasterio
!pip install rasterio --quiet
# Instalación de mapclassify
!pip install mapclassify --quiet
# Instalación de localtileserver
!pip install localtileserver --quiet
# Para mapas interactivos
import leafmap
# Para datos vectoriales
import geopandas as gpd
# Para datos raster
import rasterio
# Para gráficos
import matplotlib.pyplot as plt
# Para crear rampas de colores
from matplotlib.colors import LinearSegmentedColormap
# Para álgebra lineal
import numpy as np
# Para permitir widgets de JavaScript
from google.colab import output
output.enable_custom_widget_manager()
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/519.2 kB ? eta -:--:-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━╺━━━━━━━━━━━ 368.6/519.2 kB 11.2 MB/s eta 0:00:01 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 519.2/519.2 kB 9.5 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.1/219.1 kB 13.0 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.7/7.7 MB 71.4 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 41.4/41.4 kB 2.1 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 108.6/108.6 kB 5.3 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.7/2.7 MB 67.2 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 765.5/765.5 kB 32.8 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 194.2/194.2 kB 9.4 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 74.0/74.0 kB 3.5 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.6/1.6 MB 49.3 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17.1/17.1 MB 48.5 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.8/2.8 MB 54.5 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 267.5/267.5 kB 15.8 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 52.8/52.8 kB 3.2 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 49.5/49.5 kB 2.8 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 195.0/195.0 kB 11.6 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.3/62.3 kB 3.8 MB/s eta 0:00:00
Mapa de las Áreas de Conservación de Costa Rica¶
# Crear un geodataframe con datos y polígonos de áreas de conservación, Costa Rica
ac_gdf = gpd.read_file(
'/content/drive/MyDrive/Colab Notebooks/Datos proyecto final/2025/ACs.gpkg'
)
# Crear mapa leafmap
m = leafmap.Map(height="400px")
# Agregar capa base
m.add_basemap("CartoDB.Positron")
# Definir estilo
style = {"color": "black", "fillColor": "black", "fillOpacity": 0.1, "weight": 2}
# Agregar un geodataframe al mapa
m.add_gdf(
ac_gdf,
style=style,
layer_name="ACs",
zoom_to_layer=True
)
# Desplegar el mapa
m
Map(center=[20, 0], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zoom_out_text…
# Crear mapa leafmap
m = leafmap.Map(height="400px")
# Agregar capa de datos vectoriales
m.add_data(
ac_gdf,
column="area_ha", # columna para el mapa de coropletas
scheme="Quantiles", # esquema de clasificación
cmap="Oranges", # paleta de colores
legend_title="Áreas de Conservación"
)
# Desplegar el mapa
m
Map(center=[20, 0], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zoom_out_text…
Mapa de los avistamientos de Porthidium sp. en las Áreas de Conservación de Costa Rica¶
import leafmap
import geopandas as gpd
from pygbif import occurrences
import pandas as pd
# 🔹 Definir la especie a consultar
especie = "porthidium" # Cambia esto por la especie que desees
archivo_ac = "ACs.gpkg" # Cambia por tu archivo SHP o GeoJSON
import leafmap
import geopandas as gpd
from pygbif import occurrences
import pandas as pd
# 🔹 Definir la especie a consultar
especie = "porthidium" # Cambia esto por la especie que desees
archivo_ac = "ACs.gpkg" # Cambia por tu archivo SHP o GeoJSON
# 🔹 Cargar las Áreas de Conservación en un GeoDataFrame
ac = gpd.read_file("/content/drive/MyDrive/Colab Notebooks/Datos proyecto final/2025/ACs.gpkg").to_crs("EPSG:4326")
# 🔹 Obtener datos de ocurrencia desde GBIF
print(f"Descargando datos de GBIF para: {especie}...")
datos = occurrences.search(scientificName=especie, limit=3000) # Puedes aumentar el límite
# Convertir a DataFrame
ac_df = pd.DataFrame(datos["results"])
# 🔹 Filtrar registros con coordenadas válidas
ac_df = ac_df.dropna(subset=["decimalLatitude", "decimalLongitude"])
# Convertir los datos a un GeoDataFrame
ac_gdf = gpd.GeoDataFrame(ac_df,
geometry=gpd.points_from_xy(ac_df["decimalLongitude"], ac_df["decimalLatitude"]),
crs="EPSG:4326") # Sistema de coordenadas geográficas WGS84
# 🔹 Crear el mapa centrado en los datos de la especie
m = leafmap.Map(center=[ac_gdf.geometry.y.mean(), ac_gdf.geometry.x.mean()], zoom=5)
# Agregar los puntos al mapa
m.add_gdf(ac_gdf, layer_name=especie, marker=True, icon_colors=["red"])
# Mostrar el mapa
m
print(f"Descargando datos de GBIF para: {especie}...")
datos = occurrences.search(scientificName=especie, limit=3000) # Puedes aumentar el límite
# Convertir a DataFrame
ac_df = pd.DataFrame(datos["results"])
# 🔹 Filtrar registros con coordenadas válidas
ac_df = df.dropna(subset=["decimalLatitude", "decimalLongitude"])
# 🔹 Realizar la unión espacial (spatial join) para filtrar
ac_gdf_filtrado = gpd.sjoin(ac_gdf, ac, how="inner", predicate="intersects")
# Convertir los datos a un GeoDataFrame
ac_gdf = gpd.GeoDataFrame(ac_df,
geometry=gpd.points_from_xy(ac_df["decimalLongitude"], ac_df["decimalLatitude"]),
crs="EPSG:4326") # Sistema de coordenadas geográficas WGS84
# 🔹 Crear el mapa interactivo
m = leafmap.Map(center=[9.75, -84], zoom=7)
# Agregar Áreas de Conservación
m.add_gdf(ac, layer_name="ACs", style={"color": "green", "fillOpacity": 0.2})
# Agregar puntos filtrados
m.add_gdf(ac_gdf_filtrado, layer_name=especie, marker=True, icon_colors=["red"])
# Mostrar el mapa interactivo
m
Descargando datos de GBIF para: porthidium... Descargando datos de GBIF para: porthidium...
Map(center=[9.75, -84], controls=(ZoomControl(options=['position', 'zoom_in_text', 'zoom_in_title', 'zoom_out_…
Muestra aleatoria de registros de P. Oprhyomegas en un sitio del Sector Murciélago del Área de Conservación Guanacaste¶
import rasterio
import geopandas as gpd
import pandas as pd
from shapely.geometry import Point
from rasterio.plot import show
import matplotlib.pyplot as plt
from shapely.geometry import Point, box
# Definir rutas de los archivos
raster_path = "/content/drive/MyDrive/Colab Notebooks/Datos proyecto final/2025/2025-03-03-00_00_2025-03-03-23_59_Sentinel-2_L1C_NDVI.tiff" # Cambia esto con tu ruta
csv_path = "/content/drive/MyDrive/Colab Notebooks/Datos proyecto final/2025/Porthidium sp_CR.csv" # Cambia esto con tu ruta
# Leer el archivo ráster
raster = rasterio.open(raster_path)
# Leer el archivo CSV
df = pd.read_csv(csv_path, sep='\t')
# Cálculo del NDVI: NDVI = (NIR - roja) / (NIR + roja)
ndvi = (nir_band - red_band) / (nir_band + red_band)
ndvi = ndvi.clip(-1, 1)
# Verificar que el CSV tiene las columnas necesarias
if not {"decimalLongitude", "decimalLatitude"}.issubset(df.columns):
raise ValueError("El CSV debe contener las columnas 'decimalLongitude' y 'decimalLatitude'")
# Convertir a GeoDataFrame
geometry = [Point(xy) for xy in zip(df["decimalLongitude"], df["decimalLatitude"])]
gdf = gpd.GeoDataFrame(df, geometry=geometry, crs="EPSG:4326") # Suponiendo WGS84
# Reproyectar al CRS del ráster
gdf = gdf.to_crs(raster.crs)
# Obtener los límites del ráster como un polígono
bounds = raster.bounds # (min_x, min_y, max_x, max_y)
raster_box = box(bounds.left, bounds.bottom, bounds.right, bounds.top)
# Filtrar solo los puntos dentro del ráster
gdf_within_raster = gdf[gdf.geometry.within(raster_box)]
# Graficar el NDVI
fig, ax = plt.subplots(figsize=(10, 8))
show(ndvi, transform=raster.transform, cmap="RdYlGn", ax=ax) # Verde = más vegetación
gdf_within_raster.plot(ax=ax, color="black", markersize=10, label="Registros P. Oprhyomegas")
plt.legend()
plt.title("Registros P. Ophryomegas")
plt.colorbar(plt.cm.ScalarMappable(cmap="RdYlGn"), ax=ax, label="NDVI")
plt.show()
# Mostrar cuántos puntos fueron filtrados
print(f"Total de puntos en CSV: {len(gdf)}")
print(f"Puntos dentro del ráster: {len(gdf_within_raster)}")
Total de puntos en CSV: 814 Puntos dentro del ráster: 56