Este informe realiza un análisis exploratorio de la carpeta
Administrative, identifica los archivos espaciales
disponibles por país y nivel administrativo, cuantifica las unidades
subnacionales, revisa la estructura y completitud de sus atributos,
documenta la codificación disponible y genera un visor interactivo para
consultar las capas por país y nivel.
El análisis está diseñado para funcionar con estructuras heterogéneas. Los nombres de los campos administrativos pueden variar entre países; por ello, el informe utiliza reglas heurísticas para identificar niveles, nombres y códigos y conserva además el inventario completo de variables para facilitar la validación.
## <div style='padding:12px 16px; border:1px solid #d9e2ec; border-radius:8px; background:#f8fafc;'> <strong>Carpeta del proyecto:</strong><br> D:/Downloads <br><br> <strong>Carpeta de cartografía:</strong><br> D:/Downloads/Administrative/Administrative <br><br> <strong>Nombre previsto para la copia HTML:</strong><br> D:/Downloads/analisis_administrative_geojson_pdf_output_corregido.html </div>
Administrativese utiliza únicamente para leer los shapefiles. La copia persistente del HTML se genera en la misma carpeta donde está guardado este.Rmd.
El documento conserva runtime: shiny. El botón siguiente
intenta generar una copia HTML en la misma carpeta donde está guardado
este .Rmd.
El análisis reconoce directamente la estructura observada en la carpeta:
Administrative/
VEN/
VEN_ADM0_FN.shp
VEN_ADM1_FN.shp
VEN_ADM2_FN3.shp
PRY/
MEX/
...
La carpeta inmediatamente superior a cada .shp
identifica el país o territorio (por ejemplo VEN,
PRY, MEX) y el fragmento ADM0,
ADM1, ADM2 o ADM3 del nombre del
archivo identifica el nivel administrativo. Los sufijos posteriores,
como FN o FN3, se conservan como
variante de la capa.
shp_files <- list.files(
root_dir,
pattern = "\\.shp$",
recursive = TRUE,
full.names = TRUE,
ignore.case = TRUE
)
if (length(shp_files) == 0) {
stop("No se encontraron archivos .shp dentro de la carpeta administrative.")
}
cat("Se encontraron", length(shp_files), "archivos SHP.\n")
## Se encontraron 177 archivos SHP.
country_folders_detected <- sort(unique(toupper(basename(dirname(shp_files)))))
cat("Carpetas de país/territorio detectadas:", paste(country_folders_detected, collapse = ", "), "\n")
## Carpetas de país/territorio detectadas: ABW, AIA, AMERICAS, ARG, ATG, BES, BHS, BLM, BLZ, BMU, BOL, BRA, BRB, CAN, CHL, COL, CRI, CUB, CUW, CYM, DMA, DOM, ECU, FLK, GLP, GRD, GTM, GUF, GUY, HND, HTI, JAM, KNA, LCA, MAF, MEX, MSR, MTQ, NIC, PAN, PER, PRI, PRY, SLV, SPM, SUR, SXM, TCA, TTO, URY, USA, VCT, VEN, VGB, VIR
metadata_list <- vector("list", length(shp_files))
field_list <- vector("list", length(shp_files))
sf_cache <- vector("list", length(shp_files))
for (i in seq_along(shp_files)) {
p <- shp_files[i]
x <- safe_read_sf(p)
sf_cache[[i]] <- x
fields <- if (!is.null(x)) names(sf::st_drop_geometry(x)) else character()
country <- detect_country(p, root_dir)
level <- detect_level(p, fields)
# Identificador estable basado en la ruta relativa
rel_path <- stringr::str_remove(
normalizePath(p, winslash = "/", mustWork = FALSE),
paste0("^", stringr::fixed(normalizePath(root_dir, winslash = "/", mustWork = FALSE)), "/?")
)
shp_id <- normalize_text(tools::file_path_sans_ext(rel_path))
name_field <- first_matching_field(
fields,
c(
paste0("name_", stringr::str_extract(level %||% "", "[0-3]")),
paste0("adm", stringr::str_extract(level %||% "", "[0-3]"), "_name"),
name_candidates
),
level
)
code_field <- first_matching_field(
fields,
c(
paste0("gid_", stringr::str_extract(level %||% "", "[0-3]")),
paste0("code_", stringr::str_extract(level %||% "", "[0-3]")),
paste0("adm", stringr::str_extract(level %||% "", "[0-3]"), "_code"),
code_candidates
),
level
)
comp <- component_inventory(p)
metadata_list[[i]] <- tibble(
shp_id = shp_id,
pais = country,
nombre_pais = country_name_es(country),
nivel = level,
variante = detect_variant(p),
archivo = basename(p),
ruta_relativa = rel_path,
n_unidades = if (!is.null(x)) nrow(x) else NA_integer_,
n_campos = length(fields),
campo_nombre_detectado = name_field,
campo_codigo_detectado = code_field,
geometria = safe_geometry_type(x),
crs = safe_crs(x),
shp = TRUE,
shx = comp$presente[comp$componente == "shx"],
dbf = comp$presente[comp$componente == "dbf"],
prj = comp$presente[comp$componente == "prj"],
lectura_ok = !is.null(x)
)
field_list[[i]] <- field_profile(
x = x,
shp_id = shp_id,
country = country,
level = level
)
}
metadata <- bind_rows(metadata_list) |>
arrange(pais, nivel, archivo)
field_dictionary <- bind_rows(field_list) |>
left_join(
metadata |> distinct(pais, nombre_pais),
by = "pais"
) |>
relocate(nombre_pais, .after = pais) |>
arrange(nombre_pais, nivel, shp_id, campo)
Nota: si un nivel aparece como
NA, significa que la convención de nombres del archivo o de sus campos no permitió identificarlo automáticamente. En ese caso conviene revisar la tabla de campos y, si es necesario, ajustar la funcióndetect_level().
DT::datatable(
metadata |>
select(
pais, nivel, variante, archivo, n_unidades, n_campos,
geometria, crs, shp, shx, dbf, prj, lectura_ok
),
rownames = FALSE,
filter = "top",
options = list(pageLength = 20, scrollX = TRUE),
caption = "Inventario de archivos espaciales"
)
missing_components <- metadata |>
filter(!shx | !dbf | !prj | !lectura_ok) |>
select(pais, nivel, archivo, shx, dbf, prj, lectura_ok)
if (nrow(missing_components) == 0) {
cat("Todos los shapefiles leídos cuentan con SHX, DBF y PRJ y pudieron abrirse correctamente.")
} else {
DT::datatable(
missing_components,
rownames = FALSE,
options = list(pageLength = 15, scrollX = TRUE),
caption = "Archivos que requieren revisión"
)
}
A partir de la sigla de la carpeta, el informe reconoce el nombre del
país o territorio. La sigla se conserva como identificador técnico y se
agrega una variable nombre_pais para facilitar la lectura
de tablas, resúmenes y mapas.
country_catalog <- metadata |>
distinct(pais, nombre_pais) |>
arrange(nombre_pais)
DT::datatable(
country_catalog,
rownames = FALSE,
filter = "top",
options = list(pageLength = 30),
colnames = c(
"Código de carpeta",
"País / territorio"
),
caption = "Correspondencia entre códigos de carpeta y nombres de países o territorios"
)
unrecognized_countries <- country_catalog |>
filter(stringr::str_detect(nombre_pais, "^No identificado"))
if (nrow(unrecognized_countries) > 0) {
cat(
"\n\n**Códigos que requieren revisión manual:** ",
paste(unrecognized_countries$pais, collapse = ", "),
"\n"
)
}
##
##
## **Códigos que requieren revisión manual:** AMERICAS
country_layer_summary <- metadata |>
filter(lectura_ok) |>
group_by(pais, nombre_pais) |>
summarise(
n_capas = n(),
niveles_disponibles = paste(
sort(unique(stats::na.omit(nivel))),
collapse = ", "
),
total_unidades = sum(n_unidades, na.rm = TRUE),
.groups = "drop"
) |>
arrange(nombre_pais)
DT::datatable(
country_layer_summary,
rownames = FALSE,
filter = "top",
options = list(pageLength = 30),
caption = "Países y territorios identificados y niveles administrativos disponibles"
)
unit_counts <- metadata |>
filter(lectura_ok) |>
group_by(pais, nombre_pais, nivel) |>
summarise(
n_capas = n(),
n_unidades = sum(n_unidades, na.rm = TRUE),
.groups = "drop"
) |>
arrange(pais, nivel)
DT::datatable(
unit_counts,
rownames = FALSE,
filter = "top",
options = list(pageLength = 25),
caption = "Número de unidades administrativas por país y nivel"
)
country_summary <- metadata |>
filter(lectura_ok) |>
group_by(pais, nombre_pais) |>
summarise(
n_capas = n(),
niveles = paste(sort(unique(stats::na.omit(nivel))), collapse = ", "),
total_registros_espaciales = sum(n_unidades, na.rm = TRUE),
.groups = "drop"
) |>
arrange(pais)
DT::datatable(
country_summary,
rownames = FALSE,
filter = "top",
options = list(pageLength = 25),
caption = "Resumen general por país"
)
level_coverage <- metadata |>
filter(lectura_ok, nivel %in% c("ADM1", "ADM2", "ADM3")) |>
distinct(pais, nivel) |>
mutate(disponible = "Sí") |>
tidyr::complete(
pais,
nivel = c("ADM1", "ADM2", "ADM3"),
fill = list(disponible = "No")
) |>
tidyr::pivot_wider(
names_from = nivel,
values_from = disponible
) |>
arrange(pais)
DT::datatable(
level_coverage,
rownames = FALSE,
options = list(pageLength = 25),
caption = "Disponibilidad de niveles administrativos por país"
)
for (lev in c("ADM1", "ADM2", "ADM3")) {
countries <- metadata |>
filter(lectura_ok, nivel == lev) |>
distinct(pais) |>
arrange(pais) |>
pull(pais)
cat("\n\n### Países con ", lev, "\n\n", sep = "")
if (length(countries) == 0) {
cat("No se identificaron países con este nivel.\n")
} else {
cat(paste0("- ", countries, collapse = "\n"))
cat("\n")
}
}
##
##
## ### Países con ADM1
##
## - ABW
## - AIA
## - AMERICAS
## - ARG
## - ATG
## - BES
## - BHS
## - BLM
## - BLZ
## - BMU
## - BOL
## - BRA
## - BRB
## - CAN
## - CHL
## - COL
## - CRI
## - CUB
## - CUW
## - CYM
## - DMA
## - DOM
## - ECU
## - FLK
## - GLP
## - GRD
## - GTM
## - GUF
## - GUY
## - HND
## - HTI
## - JAM
## - KNA
## - LCA
## - MAF
## - MEX
## - MSR
## - NIC
## - PAN
## - PER
## - PRI
## - PRY
## - SPM
## - SUR
## - SXM
## - TCA
## - TTO
## - URY
## - USA
## - VCT
## - VEN
## - VGB
## - VIR
##
##
## ### Países con ADM2
##
## - ABW
## - AIA
## - AMERICAS
## - ARG
## - ATG
## - BES
## - BHS
## - BLM
## - BLZ
## - BMU
## - BOL
## - BRA
## - BRB
## - CAN
## - CHL
## - COL
## - CRI
## - CUB
## - CUW
## - CYM
## - DMA
## - DOM
## - ECU
## - FLK
## - GLP
## - GRD
## - GTM
## - GUF
## - GUY
## - HND
## - HTI
## - JAM
## - KNA
## - LCA
## - MAF
## - MEX
## - MSR
## - MTQ
## - NIC
## - PAN
## - PER
## - PRI
## - PRY
## - SLV
## - SPM
## - SUR
## - SXM
## - TCA
## - TTO
## - URY
## - USA
## - VCT
## - VEN
## - VGB
## - VIR
##
##
## ### Países con ADM3
##
## - BOL
## - CHL
## - HTI
Esta sección describe qué variables contiene cada shapefile, su tipo de dato, porcentaje de completitud, número de valores únicos y algunos valores de ejemplo.
DT::datatable(
field_dictionary,
rownames = FALSE,
filter = "top",
extensions = "Buttons",
options = list(
pageLength = 25,
scrollX = TRUE,
dom = "Bfrtip",
buttons = c("copy", "csv")
),
caption = "Diccionario de variables de las capas administrativas"
)
fields_by_layer <- field_dictionary |>
group_by(pais, nivel, shp_id) |>
summarise(
n_campos = n(),
campos = paste(campo, collapse = ", "),
.groups = "drop"
) |>
arrange(pais, nivel, shp_id)
DT::datatable(
fields_by_layer,
rownames = FALSE,
filter = "top",
options = list(pageLength = 20, scrollX = TRUE),
caption = "Campos disponibles en cada capa"
)
El siguiente procedimiento intenta identificar automáticamente un campo de nombre y un campo de código para cada capa. Cuando no los identifica, conserva el registro para facilitar la revisión manual.
codebook_list <- vector("list", nrow(metadata))
for (i in seq_len(nrow(metadata))) {
m <- metadata[i, ]
idx <- which(vapply(seq_along(shp_files), function(j) {
p <- shp_files[j]
rel_path <- stringr::str_remove(
normalizePath(p, winslash = "/", mustWork = FALSE),
paste0("^", stringr::fixed(normalizePath(root_dir, winslash = "/", mustWork = FALSE)), "/?")
)
normalize_text(tools::file_path_sans_ext(rel_path)) == m$shp_id
}, logical(1)))
if (length(idx) == 0 || is.null(sf_cache[[idx[1]]])) next
x <- sf_cache[[idx[1]]]
dat <- sf::st_drop_geometry(x)
nm <- m$campo_nombre_detectado
cd <- m$campo_codigo_detectado
unit_name <- if (!is.na(nm) && nm %in% names(dat)) as.character(dat[[nm]]) else NA_character_
unit_code <- if (!is.na(cd) && cd %in% names(dat)) as.character(dat[[cd]]) else NA_character_
codebook_list[[i]] <- tibble(
pais = m$pais,
nivel = m$nivel,
shp_id = m$shp_id,
archivo = m$archivo,
codigo_unidad = unit_code,
nombre_unidad = unit_name,
campo_codigo_origen = cd,
campo_nombre_origen = nm
)
}
codebook <- bind_rows(codebook_list) |>
distinct() |>
arrange(pais, nivel, nombre_unidad, codigo_unidad)
DT::datatable(
codebook,
rownames = FALSE,
filter = "top",
extensions = "Buttons",
options = list(
pageLength = 30,
scrollX = TRUE,
dom = "Bfrtip",
buttons = c("copy", "csv")
),
caption = "Codificación identificada para las unidades subnacionales"
)
missing_id_fields <- metadata |>
filter(
lectura_ok,
is.na(campo_nombre_detectado) | is.na(campo_codigo_detectado)
) |>
select(
pais, nivel, archivo,
campo_nombre_detectado,
campo_codigo_detectado
)
if (nrow(missing_id_fields) == 0) {
cat("Se identificaron automáticamente campos de nombre y código en todas las capas.")
} else {
DT::datatable(
missing_id_fields,
rownames = FALSE,
filter = "top",
options = list(pageLength = 20),
caption = "Capas que requieren validación manual de nombre/código"
)
}
Seleccione un país y luego el nivel administrativo. El mapa mostrará la capa correspondiente y, debajo, se presenta la tabla completa de atributos del shapefile seleccionado.
La tabla siguiente corresponde directamente a los atributos del
.dbf asociado a la capa seleccionada. Puede buscar,
filtrar, ordenar y exportar los datos.
Este resumen permite revisar la definición de las variables disponibles en la capa seleccionada.
crs_summary <- metadata |>
filter(lectura_ok) |>
count(pais, nivel, crs, name = "n_capas") |>
arrange(pais, nivel)
DT::datatable(
crs_summary,
rownames = FALSE,
filter = "top",
options = list(pageLength = 20),
caption = "Sistemas de referencia encontrados"
)
geometry_summary <- metadata |>
filter(lectura_ok) |>
count(pais, nivel, geometria, name = "n_capas") |>
arrange(pais, nivel)
DT::datatable(
geometry_summary,
rownames = FALSE,
options = list(pageLength = 20),
caption = "Tipos de geometría encontrados"
)
completeness_summary <- field_dictionary |>
group_by(pais, nivel, shp_id) |>
summarise(
n_campos = n(),
completitud_promedio = round(mean(pct_completitud, na.rm = TRUE), 1),
campos_100_pct = sum(pct_completitud == 100, na.rm = TRUE),
campos_menor_80_pct = sum(pct_completitud < 80, na.rm = TRUE),
.groups = "drop"
) |>
arrange(pais, nivel)
DT::datatable(
completeness_summary,
rownames = FALSE,
filter = "top",
options = list(pageLength = 25),
caption = "Resumen de completitud de atributos"
)
n_countries <- dplyr::n_distinct(metadata$pais)
n_layers <- nrow(metadata)
n_ok <- sum(metadata$lectura_ok)
n_bad <- sum(!metadata$lectura_ok)
cat("El directorio analizado contiene **", n_layers, " capas SHP** correspondientes a **",
n_countries, " países o grupos de archivos identificados**. ", sep = "")
El directorio analizado contiene 177 capas SHP correspondientes a 55 países o grupos de archivos identificados.
cat("De ellas, **", n_ok, "** pudieron ser leídas correctamente", sep = "")
De ellas, 174 pudieron ser leídas correctamente
if (n_bad > 0) {
cat(" y **", n_bad, "** presentaron problemas de lectura.", sep = "")
} else {
cat(".", sep = "")
}
y 3 presentaron problemas de lectura.
cat("\n\n")
for (lev in c("ADM1", "ADM2", "ADM3")) {
nlev <- metadata |>
filter(lectura_ok, nivel == lev) |>
distinct(pais) |>
nrow()
cat("- **", lev, "**: disponible en **", nlev, "** países.\n", sep = "")
}
Esta sección integra los principales hallazgos del análisis y se actualiza automáticamente con las salidas generadas a partir de las geodatabases disponibles.
La colección analizada contiene 177 capas geográficas en formato SHP, organizadas en 55 grupos territoriales o regionales. De estas capas, 174 pudieron ser leídas correctamente y 3 presentaron problemas de lectura.
De los grupos encontrados, 54 corresponden a países o
territorios identificables. Cuando existe una agrupación como
AMERICAS, esta debe interpretarse como una capa regional y
no como un país independiente.
En conjunto, las capas que pudieron ser procesadas contienen
38.088 registros espaciales. Al excluir
AMERICAS, las capas nacionales y territoriales contienen
aproximadamente 19.750 registros geográficos.
El nivel ADM1 está disponible en 53 grupos territoriales, ADM2 en 55 y ADM3 en 3.
ADM2 constituye el nivel con mayor cobertura relativa dentro de la colección, mientras que ADM3 debe utilizarse únicamente en los países donde este nivel está disponible y ha podido ser leído correctamente.
La cantidad de registros espaciales varía de forma importante entre países y territorios.
Estos valores corresponden a la suma de los registros presentes en todas las capas disponibles para cada territorio y no deben interpretarse directamente como el número de unidades de un único nivel administrativo.
La tabla permite comparar directamente la profundidad territorial disponible entre países y reconocer cuáles cuentan con un tercer nivel administrativo.
La disponibilidad limitada de ADM3 evidencia una asimetría regional y no permite asumir este nivel como base común de comparación para todos los países.
Algunos países disponen de más de una capa para el mismo nivel administrativo. Esto puede reflejar versiones alternativas, distintas fuentes o actualizaciones.
Estos casos requieren revisión antes de consolidar una geodatabase regional definitiva.
Cada geometría se encuentra acompañada por una base de atributos. El análisis caracteriza, para cada campo, su tipo de dato, número de registros, valores no nulos, porcentaje de completitud, número de valores únicos y ejemplos.
La estructura específica de cada capa puede consultarse dinámicamente en el visor interactivo, donde al seleccionar un país, nivel y capa se presenta tanto el mapa como la tabla de atributos y la estructura de variables correspondiente.
Los campos de mayor interés para la armonización regional son los relacionados con el nombre de la unidad territorial, código o identificador geográfico, nivel administrativo, relación con unidades superiores e identificadores propios de la fuente.
La tabla de codificación de unidades subnacionales permite identificar los códigos y nombres disponibles en cada capa. La estructura regional recomendada es:
País → ADM1 → ADM2 → ADM3
manteniendo para cada nivel el código original, nombre original, identificador armonizado y referencia a la unidad administrativa superior.
De las 177 capas identificadas, 174 fueron procesadas correctamente y 3 presentaron problemas de lectura.
Caso particular de Perú. Se identificó al menos un
archivo cuyo nombre contiene AMD3 en lugar de
ADM3. Este archivo debe revisarse antes de concluir si Perú
dispone o no de un tercer nivel administrativo válido dentro de la
colección. La ausencia de ADM3 en las tablas actuales puede obedecer a
un problema de nomenclatura y/o lectura, y no necesariamente a la
inexistencia de esta capa.
El visor permite explorar interactivamente las geodatabases mediante la secuencia:
País / territorio → Nivel administrativo → Capa
Para cada selección se presentan el mapa, la tabla completa de atributos y la estructura de campos de la base de datos.
El análisis confirma que la colección constituye una base geográfica regional de amplia cobertura, compuesta por 177 capas, de las cuales 174 pudieron ser procesadas correctamente.
La mayor cobertura se concentra en ADM1 y ADM2, mientras que ADM3
presenta una disponibilidad mucho más restringida. Las capas legibles
contienen 38.088 registros espaciales, o
aproximadamente 19.750 al excluir el agregado regional
AMERICAS.
El principal resultado es la construcción de un inventario regional estructurado de divisiones administrativas, que permite conocer qué información existe para cada país, hasta qué nivel territorial puede analizarse, cuántas unidades están disponibles, qué atributos las describen y qué aspectos requieren depuración.
La siguiente fase debería concentrarse en la armonización de códigos territoriales, selección de versiones principales, corrección de archivos problemáticos y construcción de una jerarquía regional común.
Además del informe HTML, este bloque genera archivos CSV que pueden utilizarse posteriormente para análisis, documentación o control de calidad.
output_dir <- file.path(project_dir, "_analysis_output")
if (!dir.exists(output_dir)) dir.create(output_dir, recursive = TRUE)
readr::write_csv(metadata, file.path(output_dir, "inventario_capas.csv"))
readr::write_csv(country_catalog, file.path(output_dir, "catalogo_paises_territorios.csv"))
readr::write_csv(unit_counts, file.path(output_dir, "conteo_unidades_por_pais_nivel.csv"))
readr::write_csv(level_coverage, file.path(output_dir, "disponibilidad_niveles.csv"))
readr::write_csv(field_dictionary, file.path(output_dir, "diccionario_campos.csv"))
readr::write_csv(codebook, file.path(output_dir, "codificacion_unidades_subnacionales.csv"))
readr::write_csv(completeness_summary, file.path(output_dir, "completitud_atributos.csv"))
cat(
"Archivos exportados en: ",
normalizePath(output_dir, winslash = "/", mustWork = FALSE),
"\n\nHTML persistente esperado en: ",
html_copy_path
)
## Archivos exportados en: D:/Downloads/_analysis_output
##
## HTML persistente esperado en: D:/Downloads/analisis_administrative_geojson_pdf_output_corregido.html
Además de los archivos CSV de análisis, el proceso genera automáticamente una copia en formato GeoJSON de cada shapefile que pudo ser leído correctamente.
Los archivos se organizan dentro de la carpeta de resultados siguiendo esta estructura:
_analysis_output/
└── geojson/
├── ARG/
├── BOL/
├── BRA/
├── COL/
├── VEN/
└── ...
Cada carpeta conserva el código del país o territorio y contiene los GeoJSON correspondientes a sus niveles administrativos. Siempre que la capa tenga un sistema de referencia definido, se transforma a EPSG:4326 (WGS84) antes de generar el GeoJSON.
geojson_root <- file.path(output_dir, "geojson")
if (!dir.exists(geojson_root)) {
dir.create(geojson_root, recursive = TRUE)
}
geojson_log <- vector("list", nrow(metadata))
for (i in seq_len(nrow(metadata))) {
m <- metadata[i, ]
# Solo se procesan capas que pudieron ser leídas correctamente
if (!isTRUE(m$lectura_ok)) {
geojson_log[[i]] <- tibble(
pais = m$pais,
nombre_pais = m$nombre_pais,
nivel = m$nivel,
archivo_origen = m$archivo,
archivo_geojson = NA_character_,
estado = "No convertido",
observacion = "La capa original no pudo ser leída"
)
next
}
shp_path <- file.path(root_dir, m$ruta_relativa)
x <- safe_read_sf(shp_path)
if (is.null(x)) {
geojson_log[[i]] <- tibble(
pais = m$pais,
nombre_pais = m$nombre_pais,
nivel = m$nivel,
archivo_origen = m$archivo,
archivo_geojson = NA_character_,
estado = "No convertido",
observacion = "No fue posible volver a abrir la capa"
)
next
}
# Carpeta específica para cada país / territorio
country_dir <- file.path(geojson_root, m$pais)
if (!dir.exists(country_dir)) {
dir.create(country_dir, recursive = TRUE)
}
# Nombre de salida: conserva el nombre original y cambia la extensión
geojson_name <- paste0(
tools::file_path_sans_ext(m$archivo),
".geojson"
)
geojson_path <- file.path(country_dir, geojson_name)
# GeoJSON debe trabajar preferentemente en WGS84 / EPSG:4326
crs_original <- sf::st_crs(x)
if (!is.na(crs_original)) {
x_out <- tryCatch(
sf::st_transform(x, 4326),
error = function(e) x
)
crs_note <- if (!is.na(sf::st_crs(x_out)$epsg) &&
sf::st_crs(x_out)$epsg == 4326) {
"Convertido a EPSG:4326"
} else {
"No fue posible transformar; se conservó el CRS original"
}
} else {
x_out <- x
crs_note <- "CRS no definido en el archivo original; se conservó sin transformación"
}
conversion_ok <- tryCatch({
sf::st_write(
x_out,
geojson_path,
driver = "GeoJSON",
delete_dsn = TRUE,
quiet = TRUE
)
TRUE
}, error = function(e) {
attr(x_out, "geojson_error") <- conditionMessage(e)
FALSE
})
if (conversion_ok) {
geojson_log[[i]] <- tibble(
pais = m$pais,
nombre_pais = m$nombre_pais,
nivel = m$nivel,
archivo_origen = m$archivo,
archivo_geojson = normalizePath(
geojson_path,
winslash = "/",
mustWork = FALSE
),
estado = "Convertido",
observacion = crs_note
)
} else {
geojson_log[[i]] <- tibble(
pais = m$pais,
nombre_pais = m$nombre_pais,
nivel = m$nivel,
archivo_origen = m$archivo,
archivo_geojson = NA_character_,
estado = "No convertido",
observacion = "Error durante la escritura del GeoJSON"
)
}
}
geojson_log <- bind_rows(geojson_log)
readr::write_csv(
geojson_log,
file.path(output_dir, "registro_conversion_geojson.csv")
)
geojson_summary <- geojson_log |>
count(estado, name = "n_capas")
DT::datatable(
geojson_summary,
rownames = FALSE,
options = list(dom = "t"),
caption = "Resumen de conversión de shapefiles a GeoJSON"
)
cat(
"\n\nGeoJSON generados en:\n",
normalizePath(
geojson_root,
winslash = "/",
mustWork = FALSE
)
)
##
##
## GeoJSON generados en:
## D:/Downloads/_analysis_output/geojson
geojson_country_summary <- geojson_log |>
filter(estado == "Convertido") |>
count(pais, nombre_pais, nivel, name = "n_geojson") |>
arrange(nombre_pais, nivel)
DT::datatable(
geojson_country_summary,
rownames = FALSE,
filter = "top",
options = list(pageLength = 30),
caption = "GeoJSON generados por país, territorio y nivel administrativo"
)
Además de las tablas CSV y los GeoJSON, el proceso genera un
informe PDF detallado de resultados dentro de la misma
carpeta _analysis_output.
La ubicación prevista es:
_analysis_output/
├── Informe_detallado_geodatabases_administrativas_Americas.pdf
├── inventario_capas.csv
├── catalogo_paises_territorios.csv
├── conteo_unidades_por_pais_nivel.csv
├── diccionario_campos.csv
├── completitud_atributos.csv
├── codificacion_unidades_subnacionales.csv
├── registro_conversion_geojson.csv
└── geojson/
El PDF se construye a partir de las salidas actualizadas del análisis. Por tanto, si se agregan, eliminan o corrigen shapefiles y se vuelve a ejecutar el Rmd, el informe reflejará los nuevos resultados.
pdf_report_file <- file.path(
output_dir,
"Informe_detallado_geodatabases_administrativas_Americas.pdf"
)
pdf_source_file <- file.path(
output_dir,
"_informe_geodatabases_pdf.Rmd"
)
# ------------------------------------------------------------
# Preparar tablas utilizadas por el informe PDF
# ------------------------------------------------------------
pdf_country_level <- unit_counts |>
select(pais, nombre_pais, nivel, n_unidades) |>
tidyr::pivot_wider(
names_from = nivel,
values_from = n_unidades,
values_fill = 0
)
for (lev in c("ADM0", "ADM1", "ADM2", "ADM3")) {
if (!lev %in% names(pdf_country_level)) {
pdf_country_level[[lev]] <- 0
}
}
pdf_country_level <- pdf_country_level |>
mutate(
Total = ADM0 + ADM1 + ADM2 + ADM3
) |>
arrange(nombre_pais)
pdf_top_countries <- country_summary |>
arrange(desc(total_registros_espaciales)) |>
slice_head(n = 15)
pdf_multiple_layers <- metadata |>
filter(lectura_ok, !is.na(nivel)) |>
count(pais, nombre_pais, nivel, name = "n_capas") |>
filter(n_capas > 1) |>
arrange(desc(n_capas), nombre_pais, nivel)
pdf_quality_issues <- metadata |>
filter(!lectura_ok | !shx | !dbf | !prj) |>
transmute(
pais,
nombre_pais,
nivel,
archivo,
SHX = ifelse(shx, "Sí", "No"),
DBF = ifelse(dbf, "Sí", "No"),
PRJ = ifelse(prj, "Sí", "No"),
lectura = ifelse(lectura_ok, "Correcta", "Con problema")
)
pdf_crs <- metadata |>
count(crs, name = "n_capas") |>
arrange(desc(n_capas))
pdf_geometry <- metadata |>
count(geometria, name = "n_capas") |>
arrange(desc(n_capas))
pdf_adm3 <- unit_counts |>
filter(nivel == "ADM3") |>
select(pais, nombre_pais, n_unidades) |>
arrange(desc(n_unidades))
pdf_fields_frequency <- field_dictionary |>
count(campo, sort = TRUE, name = "frecuencia") |>
slice_head(n = 50)
pdf_low_completeness <- completeness_summary |>
arrange(completitud_promedio) |>
slice_head(n = 20)
pdf_geojson_summary <- geojson_log |>
count(estado, name = "n_capas")
# ------------------------------------------------------------
# Indicadores dinámicos
# ------------------------------------------------------------
pdf_n_layers <- nrow(metadata)
pdf_n_ok <- sum(metadata$lectura_ok, na.rm = TRUE)
pdf_n_bad <- sum(!metadata$lectura_ok, na.rm = TRUE)
pdf_n_groups <- dplyr::n_distinct(metadata$pais)
pdf_total_records <- metadata |>
filter(lectura_ok) |>
summarise(total = sum(n_unidades, na.rm = TRUE)) |>
pull(total)
pdf_total_records_no_americas <- metadata |>
filter(lectura_ok, pais != "AMERICAS") |>
summarise(total = sum(n_unidades, na.rm = TRUE)) |>
pull(total)
pdf_level_counts <- metadata |>
filter(lectura_ok, nivel %in% c("ADM1", "ADM2", "ADM3")) |>
distinct(pais, nivel) |>
count(nivel, name = "n_paises")
pdf_get_level <- function(lev) {
x <- pdf_level_counts |>
filter(nivel == lev) |>
pull(n_paises)
if (length(x) == 0) 0 else x
}
pdf_adm1_n <- pdf_get_level("ADM1")
pdf_adm2_n <- pdf_get_level("ADM2")
pdf_adm3_n <- pdf_get_level("ADM3")
pdf_mean_completeness <- round(
mean(completeness_summary$completitud_promedio, na.rm = TRUE),
1
)
pdf_median_completeness <- round(
stats::median(completeness_summary$completitud_promedio, na.rm = TRUE),
1
)
pdf_geojson_ok <- sum(geojson_log$estado == "Convertido", na.rm = TRUE)
pdf_geojson_bad <- sum(geojson_log$estado != "Convertido", na.rm = TRUE)
# ------------------------------------------------------------
# Crear un Rmd auxiliar exclusivamente para el PDF
# ------------------------------------------------------------
pdf_rmd <- c(
'---',
'title: "Informe detallado de resultados"',
'subtitle: "Geodatabases de divisiones administrativas de países y territorios de las Américas"',
"date: \"`r format(Sys.Date(), '%d/%m/%Y')`\"",
'output:',
' pdf_document:',
' toc: true',
' number_sections: true',
' keep_tex: false',
'geometry: margin=2cm',
'fontsize: 10pt',
'lang: es',
'---',
'',
'```{r setup-pdf, include=FALSE}',
'knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)',
'library(dplyr)',
'library(tidyr)',
'library(knitr)',
'```',
'',
'# Resumen ejecutivo',
'',
paste0(
'La colección analizada contiene **', pdf_n_layers,
' capas geográficas en formato SHP**, organizadas en **',
pdf_n_groups, ' grupos territoriales o regionales**. De estas, **',
pdf_n_ok, ' pudieron ser procesadas correctamente** y **',
pdf_n_bad, ' presentaron problemas de lectura**.'
),
'',
paste0(
'Las capas legibles reúnen **',
format(pdf_total_records, big.mark = ".", decimal.mark = ","),
' registros espaciales**. Al excluir el agregado regional `AMERICAS`, ',
'el volumen corresponde a **',
format(pdf_total_records_no_americas, big.mark = ".", decimal.mark = ","),
' registros** asociados a países y territorios.'
),
'',
paste0(
'La cobertura es especialmente amplia para ADM1 y ADM2: ADM1 se identificó ',
'en **', pdf_adm1_n, ' grupos**, ADM2 en **', pdf_adm2_n,
'** y ADM3 en **', pdf_adm3_n, '**.'
),
'',
'# Cobertura por nivel administrativo',
'',
'La clasificación ADM expresa la posición jerárquica de la división territorial y no implica que las denominaciones jurídicas sean iguales entre países.',
'',
'```{r tabla-niveles}',
'pdf_level_counts |>',
' knitr::kable(',
' col.names = c("Nivel", "Países / grupos"),',
' format = "latex",',
' booktabs = TRUE',
' )',
'```',
'',
'# Inventario por país y nivel',
'',
'La siguiente tabla resume el número de registros asociados a ADM0, ADM1, ADM2 y ADM3. Cuando existe más de una capa para el mismo nivel, los registros se agregan; por ello, los valores representan el contenido de la colección y no necesariamente el número oficial vigente de unidades territoriales.',
'',
'```{r tabla-paises, results="asis"}',
'knitr::kable(',
' pdf_country_level,',
' format = "latex",',
' booktabs = TRUE,',
' longtable = TRUE,',
' row.names = FALSE',
')',
'```',
'',
'# Países y territorios con mayor volumen de registros',
'',
'```{r tabla-top, results="asis"}',
'knitr::kable(',
' pdf_top_countries,',
' format = "latex",',
' booktabs = TRUE,',
' longtable = TRUE,',
' row.names = FALSE',
')',
'```',
'',
'# Disponibilidad de ADM3',
'',
'ADM3 constituye el nivel menos homogéneo de la colección y no debe utilizarse como nivel regional común sin verificar su disponibilidad país por país.',
'',
'```{r tabla-adm3, results="asis"}',
'knitr::kable(',
' pdf_adm3,',
' format = "latex",',
' booktabs = TRUE,',
' row.names = FALSE',
')',
'```',
'',
'# Capas múltiples para un mismo nivel',
'',
'La presencia de más de una capa para un mismo país y nivel puede corresponder a versiones, fuentes, fechas o delimitaciones alternativas. Estos casos requieren clasificación antes de definir una capa regional maestra.',
'',
'```{r tabla-multiples, results="asis"}',
'if (nrow(pdf_multiple_layers) > 0) {',
' knitr::kable(',
' pdf_multiple_layers,',
' format = "latex",',
' booktabs = TRUE,',
' longtable = TRUE,',
' row.names = FALSE',
' )',
'} else {',
' cat("No se identificaron capas múltiples para un mismo nivel.")',
'}',
'```',
'',
'# Estructura de las bases de atributos',
'',
'Las geodatabases contienen información de identificación, codificación, auditoría, localización y características geométricas. Sin embargo, la estructura de campos no es uniforme entre países.',
'',
'## Campos más frecuentes',
'',
'```{r tabla-campos, results="asis"}',
'knitr::kable(',
' pdf_fields_frequency,',
' format = "latex",',
' booktabs = TRUE,',
' longtable = TRUE,',
' row.names = FALSE',
')',
'```',
'',
'# Nomencladores y codificación territorial',
'',
'El análisis muestra que el principal reto para la consolidación regional es la armonización de nomencladores. Las capas pueden contener códigos y nombres administrativos, pero estos se almacenan bajo convenciones distintas según el país o la fuente.',
'',
'Se recomienda construir una estructura regional que conserve simultáneamente:',
'',
'- código original de la unidad;',
'- nombre original;',
'- nivel administrativo;',
'- código de la unidad superior;',
'- identificador regional armonizado;',
'- campo de origen utilizado para obtener el código y el nombre;',
'- capa y fuente de procedencia.',
'',
'La secuencia recomendada para la jerarquía regional es **País → ADM1 → ADM2 → ADM3**.',
'',
'# Completitud de los atributos',
'',
paste0(
'La completitud promedio de los atributos es **', pdf_mean_completeness,
'%** y la mediana es **', pdf_median_completeness, '%**.'
),
'',
'```{r tabla-completitud, results="asis"}',
'knitr::kable(',
' pdf_low_completeness,',
' format = "latex",',
' booktabs = TRUE,',
' longtable = TRUE,',
' row.names = FALSE',
')',
'```',
'',
'# Calidad estructural de los archivos',
'',
'Los problemas detectados incluyen archivos complementarios faltantes, capas que no pueden abrirse y posibles inconsistencias de nomenclatura.',
'',
'```{r tabla-calidad, results="asis"}',
'if (nrow(pdf_quality_issues) > 0) {',
' knitr::kable(',
' pdf_quality_issues,',
' format = "latex",',
' booktabs = TRUE,',
' longtable = TRUE,',
' row.names = FALSE',
' )',
'} else {',
' cat("No se identificaron problemas estructurales.")',
'}',
'```',
'',
'El archivo de Perú cuyo nombre utiliza `AMD3` en lugar de `ADM3` debe revisarse antes de concluir sobre la ausencia o disponibilidad de un tercer nivel administrativo.',
'',
'# Sistemas de referencia',
'',
'```{r tabla-crs, results="asis"}',
'knitr::kable(',
' pdf_crs,',
' format = "latex",',
' booktabs = TRUE,',
' row.names = FALSE',
')',
'```',
'',
'Para análisis regionales se recomienda mantener intactas las fuentes originales y generar una copia de trabajo armonizada en EPSG:4326.',
'',
'# Tipos de geometría',
'',
'```{r tabla-geometrias, results="asis"}',
'knitr::kable(',
' pdf_geometry,',
' format = "latex",',
' booktabs = TRUE,',
' row.names = FALSE',
')',
'```',
'',
'# Conversión a GeoJSON',
'',
paste0(
'El proceso logró convertir **', pdf_geojson_ok,
' capas a GeoJSON**. **', pdf_geojson_bad,
' capas no fueron convertidas** por problemas de lectura u otras inconsistencias.'
),
'',
'```{r tabla-geojson, results="asis"}',
'knitr::kable(',
' pdf_geojson_summary,',
' format = "latex",',
' booktabs = TRUE,',
' row.names = FALSE',
')',
'```',
'',
'# Conclusiones',
'',
'La colección constituye una base regional de amplia cobertura y es especialmente útil para análisis ADM1 y ADM2. Su principal fortaleza es la disponibilidad de geometrías y atributos para un gran número de países y territorios.',
'',
'La principal necesidad para avanzar hacia una geodatabase regional maestra es la **armonización de códigos, nombres y relaciones jerárquicas**. Esto debe realizarse preservando los nomencladores originales y agregando un identificador regional estable.',
'',
'También se requiere definir una capa principal cuando existan versiones múltiples, reparar archivos incompletos, separar agregados regionales como `AMERICAS` y documentar fuente, versión y temporalidad.',
'',
'# Recomendaciones',
'',
'1. Definir una capa principal por país y nivel.',
'2. Construir un diccionario de nomencladores por país, nivel y capa.',
'3. Crear un identificador regional estable sin reemplazar el código original.',
'4. Reparar las capas con componentes faltantes o problemas de lectura.',
'5. Revisar específicamente la nomenclatura y estructura del posible ADM3 de Perú.',
'6. Mantener `AMERICAS` como agregado regional separado.',
'7. Generar una copia regional de trabajo en EPSG:4326.',
'8. Validar los códigos contra nomencladores oficiales nacionales.',
'9. Incorporar metadatos de fuente, fecha y versión.',
'10. Validar la integridad jerárquica ADM1–ADM2–ADM3.'
)
writeLines(
pdf_rmd,
con = pdf_source_file,
useBytes = TRUE
)
# ------------------------------------------------------------
# Renderizar a PDF
# ------------------------------------------------------------
latex_available <- nzchar(Sys.which("pdflatex")) ||
nzchar(Sys.which("xelatex")) ||
tinytex::is_tinytex()
if (latex_available) {
pdf_render_result <- tryCatch({
rmarkdown::render(
input = pdf_source_file,
output_format = "pdf_document",
output_file = basename(pdf_report_file),
output_dir = output_dir,
envir = environment(),
quiet = TRUE
)
}, error = function(e) e)
if (inherits(pdf_render_result, "error")) {
cat(
"\n\n**No fue posible generar el PDF automáticamente.**\n\n",
"Detalle: ", conditionMessage(pdf_render_result), "\n\n",
"Las demás salidas (CSV y GeoJSON) se generaron normalmente.\n"
)
} else {
cat(
"\n\n**Informe PDF generado correctamente:**\n\n",
normalizePath(
pdf_report_file,
winslash = "/",
mustWork = FALSE
),
"\n"
)
}
} else {
cat(
"\n\n**El PDF no se generó porque no se encontró una instalación de LaTeX.**\n\n",
"En RStudio puede instalar TinyTeX una sola vez ejecutando:\n\n",
"`tinytex::install_tinytex()`\n\n",
"Después, vuelva a ejecutar el documento. Los CSV y GeoJSON no dependen de LaTeX.\n"
)
}
El PDF no se generó porque no se encontró una instalación de LaTeX.
En RStudio puede instalar TinyTeX una sola vez ejecutando:
tinytex::install_tinytex()
Después, vuelva a ejecutar el documento. Los CSV y GeoJSON no dependen de LaTeX.
# El archivo auxiliar se conserva para facilitar auditoría o regeneración.
cat(
"\n\nFuente del PDF:\n",
normalizePath(
pdf_source_file,
winslash = "/",
mustWork = FALSE
)
)
Fuente del PDF: D:/Downloads/_analysis_output/_informe_geodatabases_pdf.Rmd
Antes de utilizar los resultados como insumo definitivo, se
recomienda revisar especialmente las capas en las que el nivel
administrativo, el campo de nombre o el campo de código no hayan sido
identificados automáticamente. Esto es frecuente cuando distintos países
utilizan convenciones como GID_1, ADM1_PCODE,
shapeID, COD_DEP, ID_2,
NAME_2, entre otras.
Una vez identificadas las convenciones particulares de los archivos
de la carpeta, pueden agregarse sus nombres a
name_candidates, code_candidates o a la
función detect_level() para mejorar la clasificación
automática.