Diego HH

@dieghernan.github.io

I build R packages and open-source tools for spatial data and cartography. I share maps, projects and things I learn at One World.

From postal points to areas using Voronoi polygons

Postal codes and tessellations

I recently published my project Postal codes of Spain, with estimated postal areas, an interactive map and GeoPackage downloads. But there is something we have not looked at yet: how do those address points become polygons? The answer is… **Voronoi polygons**. ## What is a Voronoi polygon? Imagine a few shops on a flat map. Which one is closest to your house? Ask that question for every location and give each shop its own color. The result is a **Voronoi diagram** , also known as a **Voronoi tessellation** , **Voronoi decomposition** , **Voronoi partition** or **Dirichlet tessellation**. This technique divides a plane into regions based on the distance to a given set of points. In our example, each shop is a **site** , or generating point. Its **Voronoi cell** , also called a **Thiessen polygon** , contains the locations at least as close to that site as to any other. Along a shared edge, two sites are equally close. Where several cells meet, there may be a tie between more sites. With ordinary straight-line distance, the edges follow perpendicular bisectors between sites. Some cells extend indefinitely, so maps usually show them clipped to a study area. The name honors Georgy Voronoi, who studied these partitions in the early twentieth century. The idea has earlier roots, including Dirichlet’s nineteenth-century work (hence the name Dirichlet tessellation). Pokojski and Pokojska (2018) review the history and geographical applications. These partitions are useful for exploring proximity to shops or public services, assigning areas to weather stations and studying spatial patterns. For example, Thiessen polygons can supply area weights when estimating rainfall from rain gauges. The underlying question is always about the chosen distance: the nearest hospital in a straight line may not be the quickest one to reach. ### A tiny example in **R** Let’s start with five points in an imaginary square, including one at its center, using just **sf** and **ggplot2**. The coordinates have arbitrary units and no geographical coordinate reference system. library(sf) library(ggplot2) # Define a common theme for the maps. background <- "#faf8f2" ink <- "#203b47" theme_set( theme_void() + theme( plot.background = element_rect(fill = background, colour = NA), panel.background = element_rect(fill = background, colour = NA), plot.title = element_text(face = "bold", colour = ink), plot.subtitle = element_text(colour = ink), plot.caption = element_text(colour = "grey40", hjust = 0), legend.position = "bottom", plot.margin = margin(10, 10, 10, 10) ) ) sites <- data.frame( site = c("A", "B", "C", "D", "E"), x = c(2, 7, 3, 8, 5), y = c(2, 3, 8, 7, 5) ) toy_points <- st_as_sf(sites, coords = c("x", "y")) study_area <- st_as_sfc(st_bbox(c(xmin = 0, ymin = 0, xmax = 10, ymax = 10))) toy_cells <- toy_points |> st_union() |> st_voronoi(envelope = study_area) |> st_collection_extract("POLYGON") |> st_intersection(study_area) ggplot() + geom_sf( data = toy_cells, fill = c("#a8dadc", "#f4c89a", "#c6b8df", "#bfd59b", "#f2b5c4"), colour = "white", linewidth = 1 ) + geom_sf(data = toy_points, colour = "#203b47", size = 3) + geom_text( data = sites, aes(x, y, label = site), nudge_y = 0.5, colour = "#203b47", fontface = "bold" ) + coord_sf(datum = NA) + labs( title = "Five points, five Voronoi cells", subtitle = "Each location belongs to the closest site" ) Five points, five cells. The new point E at `(5, 5)` has a bounded cell surrounded by the other four. The outer cells extend beyond the square and are clipped to its edges. `st_union()` collects the points into one geometry, `st_voronoi()` builds the diagram and `st_intersection()` clips it to our square. The colors help us tell the cells apart. We have not assigned any postal codes yet. The diagram knows nothing about roads, rivers or administrative boundaries. As we move to postal points, we will need to make our choices about those boundaries explicit. ## Before we start: what are we estimating? The Spanish project uses CartoCiudad address points with their postal codes. It follows Goerlich (2022), who develops an idea presented by Sánchez (2020). The updated online version describes the method in detail. For Slovenia, we will use GISCO’s postal points, retrieved with **giscoR**. There is an important difference here: a representative point for a code tells us much less than thousands of addresses with that code. Detailed addresses help the partition follow their distribution more closely, although proximity alone still cannot reproduce postal delivery decisions. We have not established that Slovenian postal areas should follow municipal boundaries. Imposing those boundaries is a modeling choice for this exercise, inspired by the Spanish workflow, rather than a verified property of the Slovenian postal system. __This post explains the workflow. The polygons we create are not official postal boundaries, and this exercise does not validate the GISCO postal data. ## Getting the data First, let’s get the municipal boundaries and postal points. We use the 2024 release of both datasets. library(giscoR) library(sf) library(dplyr) municipalities <- gisco_get_lau( year = 2024, country = "SI", epsg = 3035 ) |> select(GISCO_ID, LAU_NAME) |> st_make_valid() raw_points <- gisco_get_postalcodes( year = 2024, country = "SI", epsg = 3035 ) We work in **ETRS89 / LAEA Europe (EPSG:3035)** , a projected coordinate system with coordinates in meters. This makes the 200 m and 50 m margins used later meaningful. Voronoi uses Euclidean distances in this plane, rather than road distances. The projection does not preserve every distance exactly either. Before we start drawing, what have we actually downloaded? postal_points <- raw_points |> mutate(POSTCODE = trimws(as.character(POSTCODE))) |> filter(!is.na(POSTCODE), POSTCODE != "") |> select(POSTCODE) code_counts <- postal_points |> st_drop_geometry() |> count(POSTCODE, name = "points_per_code") knitr::kable(data.frame( Indicator = c( "Municipalities", "Downloaded points", "Points with a postal code", "Distinct postal codes", "Codes represented by one point" ), Value = c( nrow(municipalities), nrow(raw_points), nrow(postal_points), nrow(code_counts), sum(code_counts$points_per_code == 1) ) )) Indicator | Value ---|--- Municipalities | 212 Downloaded points | 557 Points with a postal code | 557 Distinct postal codes | 557 Codes represented by one point | 557 These counts give us a first look at the file, but they are not a quality check. They do not establish whether every code is present or whether the coordinates are correct. With one point per code, we will be drawing areas around representatives rather than estimating them from individual addresses. Two points at exactly the same coordinates cannot generate separate Voronoi cells. We sort by postal code and keep the first point at each location, as in the original workflow. This is a reproducible tie-breaking rule, not evidence that the retained code is correct. The counts below show how many records we remove and how many locations have conflicting codes. postal_points <- postal_points |> arrange(POSTCODE) conflicts <- postal_points |> group_by(geometry) |> summarise(codes = n_distinct(POSTCODE), .groups = "drop") |> filter(codes > 1) duplicates <- duplicated(postal_points$geometry) knitr::kable(data.frame( Indicator = c("Duplicate locations removed", "Locations with conflicting codes"), Value = c(sum(duplicates), nrow(conflicts)) )) Indicator | Value ---|--- Duplicate locations removed | 79 Locations with conflicting codes | 16 postal_points <- postal_points[!duplicates, ] ## Starting with Ljubljana Let’s pick Ljubljana and keep the points inside it. We use `st_within()` so that a point exactly on a shared boundary is not assigned to both neighboring municipalities. city <- municipalities |> filter(LAU_NAME == "Ljubljana") city_points <- st_filter(postal_points, city, .predicate = st_within) base_map <- ggplot() + geom_sf(data = city, fill = "#e8e4d8", colour = ink, linewidth = 0.4) gisco_attr <- gisco_attributions() base_map + geom_sf(data = city_points, colour = "#bd5137", size = 2) + labs( title = "Ljubljana: the starting point", subtitle = "A municipal boundary and points with postal codes", caption = gisco_attr ) ### Building the cells Each cell contains locations at least as close to its generating point as to any other point. Cell boundaries occur where distances to two points are equal. For this first demonstration, we keep all interior points, without the margins used in the national workflow. cells <- city_points |> st_union() |> st_voronoi(envelope = st_as_sfc(st_bbox(city))) |> st_collection_extract("POLYGON") |> st_sf(geometry = _) base_map + geom_sf(data = cells, fill = NA, colour = "#bd5137", linewidth = 0.5) + geom_sf(data = city_points, colour = ink, size = 2) + labs( title = "An area of proximity for every point", subtitle = "Cells still extend beyond the municipal boundary", caption = "Own elaboration using data from Eurostat / GISCO, 2024" ) `st_union()` gathers the points so we build one diagram. One detail to keep in mind: `envelope` supplies a working extent, but **does not clip** the cells to the municipality. We need an intersection for that. ### Clipping and recovering postal codes It would be convenient if cells came out in the same order as the input points, but we should not rely on that. Instead, we recover each cell’s code with a spatial relationship before clipping. Each cell must contain exactly one generating point. point_matches <- st_intersects(cells, city_points) cells$POSTCODE <- city_points$POSTCODE[unlist(point_matches)] clipped_geometry <- st_intersection(st_geometry(cells), st_geometry(city)) city_cells <- st_sf( POSTCODE = cells$POSTCODE[attr(clipped_geometry, "idx")[, 1]], geometry = clipped_geometry ) city_areas <- city_cells |> group_by(POSTCODE) |> summarise(.groups = "drop") |> st_make_valid() city_colours <- setNames( hcl.colors(nrow(city_areas), palette = "Dark 3"), sort(city_areas$POSTCODE) ) city_labels <- st_sf( POSTCODE = city_areas$POSTCODE, geometry = st_point_on_surface(st_geometry(city_areas)) ) ggplot(city_areas) + geom_sf(aes(fill = POSTCODE), colour = background, linewidth = 0.6) + geom_sf( data = city_points, shape = 21, fill = ink, colour = background, size = 2.4 ) + geom_sf_text( data = city_labels, aes(label = POSTCODE), colour = ink, fontface = "bold", size = 3.5 ) + scale_fill_manual(values = city_colours, guide = "none") + coord_sf(datum = NA) + labs( title = "From points to estimated areas", subtitle = "Cells clipped and merged by postal code", caption = "Teaching example · Not official postal boundaries" ) We intersect the geometry columns, then use the intersection’s `idx` matrix to recover each cell’s postal code. This keeps the attribute assignment explicit. Label positions are also calculated from geometries alone. Grouping by postal code with `summarise()` merges the geometries. With many addresses sharing a code, their cells would dissolve into one geometry, possibly with several separate parts. ## From one municipality to the whole country Ljubljana is done. Now let’s repeat the operation for each component of each municipality. Municipalities can have separate pieces: normalizing to `MULTIPOLYGON` and then casting to `POLYGON` lets us handle them independently, without automatically extending a code to a piece with no points. The municipality ID and name apply to every component, so we mark those attributes as spatially constant before splitting the geometries. components <- municipalities |> st_set_agr("constant") |> st_cast("MULTIPOLYGON") |> st_cast("POLYGON") |> mutate(component_id = row_number()) ### Why use an inward margin? In the Spanish project, we first select addresses within an inward margin of **200 m**. If none are found, we try **50 m**. This reduces the influence of points close to a boundary, where small mismatches between layers may affect their assignment. There is no universal rule behind these numbers. Goerlich describes a 20 m margin. The 200 m and 50 m choices come from my implementation. We keep them here to explain that workflow, although sparse postal points make it easier to exclude useful information. Comparing other margins would be a useful next step. select_points <- function(component, postal_points) { for (margin in c(200, 50)) { interior <- st_buffer(st_geometry(component), -margin) if (all(st_is_empty(interior))) next selected_points <- st_filter(postal_points, interior, .predicate = st_within ) if (nrow(selected_points) > 0) { return(selected_points) } } postal_points[0, ] } The margin only selects points: the resulting areas cover the **original** component, not the smaller buffered geometry. If both attempts fail, we leave the component unassigned. We do not borrow the nearest point from outside the municipality. ### Three cases, one function There are three cases to handle. With no points, we keep the component and assign `NA`. If all selected points share a code, we assign it to the whole component. We only need Voronoi when several codes are present: no need to split an area that already has a single assignment. build_areas <- function(component, postal_points) { selected_points <- select_points(component, postal_points) codes <- unique(selected_points$POSTCODE) if (length(codes) <= 1L) { component$POSTCODE <- if (length(codes)) codes else NA_character_ return(component) } cells <- selected_points |> st_union() |> st_voronoi(envelope = st_as_sfc(st_bbox(component))) |> st_collection_extract("POLYGON") |> st_sf(geometry = _) point_matches <- st_intersects(cells, selected_points) cells$POSTCODE <- selected_points$POSTCODE[unlist(point_matches)] clipped_geometry <- st_intersection( st_geometry(cells), st_geometry(component) ) result <- st_sf( POSTCODE = cells$POSTCODE[attr(clipped_geometry, "idx")[, 1]], geometry = clipped_geometry ) |> group_by(POSTCODE) |> summarise(.groups = "drop") |> st_make_valid() result$GISCO_ID <- component$GISCO_ID result$LAU_NAME <- component$LAU_NAME result$component_id <- component$component_id result } result <- lapply(seq_len(nrow(components)), function(i) { build_areas(components[i, ], postal_points) }) |> bind_rows() ### Handling unassigned areas Can we fill any of the gaps? A component without points may belong to a municipality whose other components have information. We recover its code only if the municipality has **one distinct recovered code**. This is an imputation from the available data, not proof that the municipality really has just one code. unique_codes <- result |> st_drop_geometry() |> filter(!is.na(POSTCODE)) |> distinct(GISCO_ID, POSTCODE) |> add_count(GISCO_ID) |> filter(n == 1) unassigned_before <- sum(is.na(result$POSTCODE)) index <- match(result$GISCO_ID, unique_codes$GISCO_ID) recoverable <- is.na(result$POSTCODE) & !is.na(index) result$POSTCODE[recoverable] <- unique_codes$POSTCODE[index[recoverable]] Before drawing the final map, let’s tidy up the geometries and keep only surfaces. Repairs can produce collections containing residual lines or points, which do not belong in an area layer. Dissolving by postal code also removes municipal boundaries between areas sharing that code. keep_surfaces <- function(x) { geometry <- st_make_valid(st_geometry(x)) geometry <- lapply(geometry, function(part) { if (!inherits(part, c("POLYGON", "MULTIPOLYGON", "GEOMETRYCOLLECTION"))) { return(st_multipolygon()) } surfaces <- suppressWarnings(st_collection_extract(st_sfc(part), "POLYGON")) if (length(surfaces) == 0L || all(st_is_empty(surfaces))) { return(st_multipolygon()) } st_cast(st_union(surfaces), "MULTIPOLYGON", warn = FALSE)[[1]] }) st_geometry(x) <- st_sfc(geometry, crs = st_crs(x)) x[!st_is_empty(x), ] } unassigned <- result |> filter(is.na(POSTCODE)) |> keep_surfaces() postal_areas <- result |> filter(!is.na(POSTCODE)) |> group_by(POSTCODE) |> summarise(.groups = "drop") |> keep_surfaces() |> st_cast("MULTIPOLYGON") ## The complete map For the national overview, let’s color the areas by the first digit of their code. This keeps the map readable, without claiming that we have validated a classification of postal regions. Gray is reserved for unassigned components, with their own legend entry. postal_colours <- c( "1" = "#007F7F", "2" = "#E87500", "3" = "#3B6FB6", "4" = "#A64E9A", "5" = "#649A32", "6" = "#D9AF00", "8" = "#B94350", "9" = "#6255A4", "Unassigned" = "#727A80" ) postal_groups <- sort(unique(substr(postal_areas$POSTCODE, 1, 1))) ggplot() + geom_sf(data = municipalities, fill = "#d5d2ca", colour = NA) + geom_sf( data = postal_areas, aes(fill = substr(POSTCODE, 1, 1)), colour = background, linewidth = 0.15 ) + geom_sf( data = unassigned, aes(fill = "Unassigned"), colour = background, linewidth = 0.15 ) + scale_fill_manual( values = postal_colours, name = "Postal code group", breaks = c(postal_groups, "Unassigned"), labels = c(paste0(postal_groups, "xxx"), "Unassigned") ) + coord_sf(datum = NA) + labs( title = "Slovenia, point by point", subtitle = "Proximity areas clipped to municipal boundaries", caption = paste("Eurostat / GISCO, 2024 · Own elaboration", "Dark grey: unassigned areas · Estimated boundaries, not official", sep = "\n" ) ) ## Checking the result The map is ready, but let’s check the geometry before going further. Are the polygons valid? Do assigned and unassigned areas cover the territory? Are there any appreciable overlaps? We allow one square meter for numerical differences in the geometry operations. territory <- st_union(st_geometry(municipalities)) coverage <- st_union(c(st_geometry(postal_areas), st_geometry(unassigned))) difference_m2 <- as.numeric(sum(st_area(st_sym_difference(territory, coverage)))) overlap_m2 <- as.numeric( sum(st_area(postal_areas)) + sum(st_area(unassigned)) - sum(st_area(coverage)) ) knitr::kable(data.frame( Indicator = c( "Codes with an estimated area", "Components assigned by municipal recovery", "Unassigned records", "Unassigned area (km²)", "Coverage difference (m²)", "Overlap (m²)" ), Value = round(c( nrow(postal_areas), sum(recoverable), nrow(unassigned), as.numeric(sum(st_area(unassigned))) / 1e6, difference_m2, overlap_m2 ), 4) )) Indicator | Value ---|--- Codes with an estimated area | 468.0000 Components assigned by municipal recovery | 4.0000 Unassigned records | 6.0000 Unassigned area (km²) | 150.2033 Coverage difference (m²) | 0.0000 Overlap (m²) | -0.0002 These checks verify the geometry, **not postal accuracy**. We could cover an entire country with valid polygons and still have a poor approximation of its delivery areas. ## Back to Spain We now have the full sequence: points, proximity areas, clipping and merging by postal code. The same steps work with much more detailed addresses, such as the CartoCiudad data in my Spanish postal codes project. The input data matters as much as the algorithm. With one representative point per code, Voronoi mainly maps distances between representatives. With address points, it can better approximate where each code’s addresses are concentrated. It still knows nothing about delivery routes, physical barriers or official boundaries. We have not compared these results with an independent postal source for Slovenia. Before using them for analysis, we would need to review coverage, coordinates, the relationship with municipalities and sensitivity to the margins. For now, we have a working example that shows how the polygons are built, one step at a time. ## References * Pokojski, W. & Pokojska, P. (2018). Voronoi diagrams – inventor, method, applications. _Polish Cartographical Review_ , _50_(3), 141–150. https://doi.org/10.2478/pcr-2018-0009. * Goerlich, F. J. (2022). Elaboración de un mapa de códigos postales de España con recursos libres. Cómo evitar pagar por disponer de información de referencia. _Working Papers Ivie_ , no. 2022-3. València: Ivie. https://doi.org/10.12842/WPIVIE_0322. Updated online version: https://www.uv.es/goerlich/Ivie/CodPost.html. * Sánchez, J. I. (2020, November 13). “Así hice el mapa de los códigos postales de España con SIG y datos abiertos.” NoSoloSIG. https://www.nosolosig.com/articulos/asi-hice-el-mapa-de-los-codigos-postales-de-espana-con-sig-y-datos-abiertos. * Hernangómez, D. (2026). Códigos postales de España [Dataset]. https://doi.org/10.5281/zenodo.23698418. https://dieghernan.github.io/codigospostales/.

dieghernan.github.io

MITECO data and firefly cartography with R

Large forest fires in Spain

**GIFs** provides a dataset and an infographic of large forest fires in Spain, compiled from the statistics of the Ministry for the Ecological Transition and the Demographic Challenge (MITECO). Here, GIF means _gran incendio forestal_ : a fire with at least 500 hectares of forest area burned. The project uses **R** to prepare the data and create a map inspired by Dominic Royé’s firefly cartography. ## Data and interpretation Records span February 27, 2010 to September 26, 2026. These dates mark the first and last records in the dataset; they do not represent complete annual coverage. The dataset is not a real-time fire monitoring service. The CSV contains municipality and province names, INE codes, dates and forest area burned in hectares. It combines final statistics for 2010–2021 with provisional reports for 2022–2026. Dates recorded as December 31, 2012 identify the year only, rather than a confirmed day of ignition. Map points use municipal reference locations from **mapSpain** , not exact fire locations or perimeters. Circle sizes aggregate forest area burned at each reference location. ## Downloads and reuse The project website provides the infographic, a CSV download and citation information. Consult its documented methodology for collection decisions, source limitations and validation. Machine-readable field descriptions are available in the Croissant metadata. Data reuse follows the MITECO legal notice, including attribution and preservation of the original conditions and metadata. The repository’s MIT license applies to its code and documentation. The project is independent and does not imply MITECO participation or endorsement. Cite the dataset using its Zenodo DOI.

dieghernan.github.io

Estimated postal code areas from open data

Postal codes of Spain

**Códigos postales de España** provides estimated postal code areas for Spain, built from open address data from CartoCiudad and administrative boundaries. Explore the results on interactive maps and download the data for use in **R** , **Python** or **QGIS**. ## Background Postal code polygons are useful for linking addresses and statistics to areas on a map. According to Goerlich (2022), the Correos polygons available through CartoCiudad stopped being offered for download in early 2017. This project explores a reproducible alternative based on open address data. An early reference is José Ignacio Sánchez’s 2020 article in NoSoloSIG, which describes how to estimate postal areas from CartoCiudad addresses using GIS tools. This project applies the approach developed in Francisco José Goerlich’s 2022 work, with its own choices for selecting addresses and handling areas without an assignment. ## Maps and data The project website includes interactive maps and two GeoPackage downloads: a full version for spatial analysis and a simplified version for visualization. Both include postal code areas and administrative areas pending assignment. The data is shared under a Creative Commons Attribution 4.0 International license. ### Interactive map Search for a postal code or address, select a two-digit prefix and compare the estimated areas with CartoCiudad. The viewer uses the simplified data and does not display areas pending assignment. Its controls are in Spanish. Open the map in a full window Viewer guide ## Methodology Following Goerlich’s approach, the workflow treats each separate component of an administrative area independently. When the selected addresses share a single postal code, it assigns that code to the whole component. When several codes are present, it builds Voronoi polygons around the address locations, clips them to the administrative boundary and merges areas with the same code. The final polygons are dissolved by postal code across provinces. This implementation selects addresses using an inward buffer of 200 m, with a second attempt at 50 m if none are found, rather than the 20 m described by Goerlich. Components without selected addresses remain unassigned unless the municipality has a single postal code recovered from its other components. The administrative boundaries come from LAU2boundaries4Spain, by Francisco José Goerlich and Pedro Pérez Vázquez. These are estimated areas, not official postal boundaries. Coverage depends on the source addresses and some areas remain unassigned. The repository documents the sources, methodology, limitations and steps to reproduce the data. ## References * Instituto Geográfico Nacional / Centro Nacional de Información Geográfica (IGN/CNIG). (n.d.). _CartoCiudad_ [Dataset]. Data portal. * Instituto Geográfico Nacional / Centro Nacional de Información Geográfica (IGN/CNIG). (n.d.). _Servicios de visualización de CartoCiudad: direcciones y códigos postales_. WMS documentation. * Correos. (n.d.). _Base de datos de códigos postales y capa cartográfica_. Product information. * Goerlich, F. J. (2022). _Elaboración de un mapa de códigos postales de España con recursos libres. Cómo evitar pagar por disponer de información de referencia_. Working Papers Ivie, no. 2022-3. Ivie. doi:10.12842/WPIVIE_0322. * Goerlich, F. J., & Pérez Vázquez, P. (2025). _Base de datos histórica de contornos municipales de España –LAU2boundaries4Spain–_ (version v2) [Dataset]. Zenodo. doi:10.5281/zenodo.15345101. * Hernangómez, D. (2026). _Códigos postales de España_ (version 1.0.0) [Dataset]. Zenodo. doi:10.5281/zenodo.23698418. * Sánchez, J. I. (2020, November 13). _Así hice el mapa de los códigos postales de España con SIG y datos abiertos_. NoSoloSIG. Article.

dieghernan.github.io

A database with geocodes

Country codes & organizations

Complete database of countries and territories, their different country codes under common standards (ISO-3166, GEC _(formerly FIPS)_ , M49 (_UN_), STANAG (_NATO_), NUTS (_EU_), etc.) and their membership in different international organizations. _Note that blanks are presented as_ `""` _instead of_ `NA` _since ISO-3166-ALPHA 2 for Namibia is_ **NA**. **Vignette: Using country codes** ## A. Country codes `.csv` Main `.csv` file (link) containing: * Country and regional codes * Currency, dependency status and sovereignty information * Names in English and Spanish as provided by Unicode CLDR * Additional information (demographics, capital, area, etc.) ### Codes included Field | Description | Source | Notes ---|---|---|--- `ISO_3166_1` | ISO 3166-1 numeric | Wikipedia | `ISO_3166_2` | ISO 3166-1 alpha-2 | Wikipedia | `ISO_3166_3` | ISO 3166-1 alpha-3 | Wikipedia | `FIPS_GEC` | Geopolitical Entities and Codes (GEC) | CIA World Factbook | Formerly FIPS 1PUB 10-4 (historical CIA reference) `STANAG` | STANAG 1059 Country Codes | CIA World Factbook | Used by NATO `M49` | UN Country Code | UN Stats | `NUTS` | NUTS 0 code | Wikipedia | Used by EU `geonameId` | geonameId | geonames | `continentcode` | geonames Continent Code | geonames | `regioncode` | UN Regional Code | UN Stats | `interregioncode` | Interregional Code | UN Stats | `subregioncode` | Subregion Code | UN Stats | `ISO_3166_3.sov` | Sovereign code | Wikipedia, Statoids | If non-independent ### Other information included * Currency * Dependency status * Names in English and Spanish for countries, continents, regions and capitals * Population, area (km2) and developed region ## B. International organizations `.csv` A single `.csv` file (link) describing the membership status of each country across 186 international organizations. Field | Description ---|--- `ISO_3166_2` | Matches Countrycodes `.csv` `ISO_3166_3` | Matches Countrycodes `.csv` `NAME.EN` | Matches Countrycodes `.csv` `source` | Main data source `org_name` | Name of the organization `org_id` | Abbreviation or internal ID `org_member` | Membership status ## C. Full JSON file `.json` This `.json` file (link) combines the previous files: [ ... { "ISO_3166_1": 12, "ISO_3166_2": "DZ", "ISO_3166_3": "DZA", "ISO_Official": true, "FIPS_GEC": "AG", "STANAG": "DZA", "M49": 12, "geonameId": 2589581, "continentcode": "AF", "regioncode": 2, "subregioncode": 15, "currency": "DZD", "independent": true, "NAME.EN": "Algeria", "CONTINENT.EN": "Africa", "REGION.EN": "Africa", "SUBREGION.EN": "Northern Africa", "CAPITAL.EN": "Algiers", "NAME.ES": "Argelia", "CONTINENT.ES": "Africa", "REGION.ES": "África", "SUBREGION.ES": "África septentrional", "CAPITAL.ES": "Argel", "pop": 34586184, "area_km2": 2381740, "Developed": "Developing", "org_id": ["ABEDA", "ACP", "ADB", "AFDB", "AFESD", "AG", "AL", ... ], "org_member": ["member", null, null, "member", "member", null, "member", ... ] }, ... ] A complementary function (intended to be used in **R**) has been developed: ISO_memcol = function(df, #Input dataframe orgtosearch #org id ) { ind = match(orgtosearch, unlist(df[1, "org_id"])) or = lapply(1:nrow(df), function(x) unlist(df[x, "org_member"])[ind]) or = data.frame(matrix(unlist(or)), stringsAsFactors = F) names(or) = orgtosearch df2 = as.data.frame(cbind(df, or, stringsAsFactors = F)) return(df2) } ## D. Data sources * Wikipedia, the free encyclopedia * ISO-3166 * NUTS * The World Factbook - CIA (historical source used for this dataset) * United Nations Statistical Division * geonames * REST COUNTRIES * Unicode Common Locale Data Repository (CLDR) Project * http://www.statoids.com/

dieghernan.github.io

A compilation of celestial data files

Celestial data

This project provides several datasets of celestial objects as of the J2000 epoch in GeoJSON and GeoPackage formats. The original files were provided by the d3-celestial plugin (Frohn 2015) under BSD 3-Clause. The datasets produced by this project consist of the same data provided by the d3-celestial plugin processed with the **R** package **sf** (Pebesma 2018) to ensure its validity: * The spatial data objects are bounded by \\([-180, -90, 180, 90]\\). * Data are provided in WGS 84 - World Geodetic System 1984 (EPSG:4326). * All geometries are valid as per ST_IsValid (GEOS **3.9.3**). ## Distribution The data can be accessed from several API endpoints: <!-- From GitHub --> https://raw.githubusercontent.com/dieghernan/celestial_data/main/data/mw.min.geojson <!-- From the website --> https://dieghernan.github.io/celestial_data/data/mw.min.geojson <!-- From jsDelivr --> https://cdn.jsdelivr.net/gh/dieghernan/celestial_data@main/data/mw.min.geojson ## Data Data are provided in GeoJSON (`*.geojson`) and GeoPackage (`*.gpkg`) formats. Additionally, for GeoJSON files a minified version (`*.min.geojson`) is also provided. The data source can be found in the corresponding GitHub repo. List of files provided: * `asterisms.geojson` * `asterisms.gpkg` * `asterisms.min.geojson` * `constellations.borders.cn.geojson` * `constellations.borders.cn.gpkg` * `constellations.borders.cn.min.geojson` * `constellations.borders.geojson` * `constellations.borders.gpkg` * `constellations.borders.min.geojson` * `constellations.borders.min.min.geojson` * `constellations.bounds.cn.geojson` * `constellations.bounds.cn.gpkg` * `constellations.bounds.cn.min.geojson` * `constellations.bounds.geojson` * `constellations.bounds.gpkg` * `constellations.bounds.min.geojson` * `constellations.cn.csv` * `constellations.cn.geojson` * `constellations.cn.gpkg` * `constellations.cn.min.geojson` * `constellations.csv` * `constellations.geojson` * `constellations.gpkg` * `constellations.lines.cn.geojson` * `constellations.lines.cn.gpkg` * `constellations.lines.cn.min.geojson` * `constellations.lines.geojson` * `constellations.lines.gpkg` * `constellations.lines.min.geojson` * `constellations.min.geojson` * `dsonames.cn.csv` * `dsonames.csv` * `dsos.14.geojson` * `dsos.14.gpkg` * `dsos.14.min.geojson` * `dsos.20.geojson` * `dsos.20.gpkg` * `dsos.20.min.geojson` * `dsos.6.geojson` * `dsos.6.gpkg` * `dsos.6.min.geojson` * `dsos.bright.geojson` * `dsos.bright.gpkg` * `dsos.bright.min.geojson` * `lg.geojson` * `lg.gpkg` * `lg.min.geojson` * `messier.geojson` * `messier.gpkg` * `messier.min.geojson` * `mw.geojson` * `mw.gpkg` * `mw.min.geojson` * `starnames.cn.csv` * `starnames.csv` * `stars.14.geojson` * `stars.14.gpkg` * `stars.14.min.geojson` * `stars.6.geojson` * `stars.6.gpkg` * `stars.6.min.geojson` * `stars.8.geojson` * `stars.8.gpkg` * `stars.8.min.geojson` See additional details in the data description. ## Citation Please cite these datasets as: Frohn, O., & Hernangómez, D. (2023). Celestial Data [Data set]. https://doi.org/10.5281/zenodo.7561601 A BibTeX entry: @misc{frohnhernangomez:2023, title = {Celestial Data}, author = {Olaf Frohn and Diego Hernangómez}, year = 2023, doi = {10.5281/zenodo.7561601}, url = {https://dieghernan.github.io/celestial_data/}, } ## References Frohn, Olaf. 2015. “d3-celestial” https://github.com/ofrohn/d3-celestial/. Pebesma, Edzer. 2018. “Simple Features for R: Standardized Support for Spatial Vector Data.” _The R Journal_ 10 (1): 439446. https://doi.org/10.32614/RJ-2018-009.

dieghernan.github.io

A Pebble project

Sfera

**Project discontinued** due to the shutdown of Pebble. **Sfera** for Pebble Time Round is a highly customizable watch face that gets the most out of the smartwatch’s capabilities. Set your preferences and enjoy this beautifully designed watch face. Download from Rebble Appstore ## Features * Clock mode: * Analog: Classic analog watch face * Digital: Centered hour and minute display based on analog movement * Dual: Analog and digital, all in one * Mix: Digital hour and analog minute * Autodetection of 12h/24h mode based on your watch settings ## Take your pick * Date - Get the weekday based on the language set on your Pebble. * Dots as minute markers - choose your color * Battery level displayed beautifully as an arc near the bezel. Choose your color and, below 20%, it turns red! * Weather: Current conditions in °C or °F. * Choose your weather provider: * Yahoo.com _No API key required at this moment_ * Wunderground * OpenWeatherMap * Historical integration with Master Key (pmkey.xyz) * Location based on your selected weather provider * Night theme displayed between sunset and sunrise ## Internationalization Automatic weekday translation is supported for: * English * Spanish * German * French * Portuguese * Italian ## Future developments * 12/24h mode * Night theme * Several weather providers available * Master Key (pmkey.xyz) was implemented for easy API key management ## Screenshots ## Attributions ### Fonts * Weather Icons by Eric Flowers, modified and fitted to the regular alphabet instead of Unicode values. * Custom font for icons created via **Fontastic** downloaded from fontsgeek.com ### Weather providers ### Others Master Key (pmkey.xyz) was a service for Pebble users that managed API keys through a unique PIN. ## License Developed under license MIT. **Made in Madrid, Spain ❤️**

dieghernan.github.io

A Pebble project

Bzel

**Project discontinued** due to the shutdown of Pebble. **Bzel** integrates the bezel into your watch face. Display minutes as digits, as a moving dot or as a fill in the bezel. Download from Rebble Appstore ## Features * Clock mode: * Digital: Minute display based on analog movement * Dot: Moving dot as a minute marker * Bezel: A bar moving around the bezel as a minute marker * Autodetection of 12h/24h mode based on your watch settings ## Take your pick * Pebble Health: Display daily steps. * Date - Get the weekday based on the language set on your Pebble. * Weather: Current conditions in °C or °F. * Choose your weather provider: * Yahoo.com _No API key required at this moment_ * Wunderground * OpenWeatherMap * Historical integration with Master Key (pmkey.xyz) * Location based on your selected weather provider * Night theme displayed between sunset and sunrise ## Internationalization Automatic weekday translation is supported for: * English * Spanish * German * French * Portuguese * Italian ## Future developments * Location for weather * Square support * New minute mode: bezel * Steps * More health metrics ## Screenshots ## Attributions ### Fonts * Weather Icons by Eric Flowers, modified and fitted to the regular alphabet instead of Unicode values. * Custom font for icons created via Fontastic. * Gotham Fonts downloaded from fontsgeek.com ### Weather providers ### Others Master Key (pmkey.xyz) was a service for Pebble users that managed API keys through a unique PIN. ## License Developed under license MIT. **Made in Madrid, Spain ❤️**

dieghernan.github.io

A Pebble project

TextWatch Clima

**Project discontinued** due to the shutdown of Pebble. **TextWatch Clima** upgrades the classic TextWatch watch face by adding a set of new capabilities. **Available for all the Classic, Time and Pebble 2 models** Download from Rebble Appstore ## Features * Exact time in natural language * Autofit to screen ## Take your pick * Date format: Day Month / Month Day * Fuzzy time option * Text animation * Language * Spanish * English * German (thanks to rodher) * French * Italian * Portuguese * Norwegian * Danish * Swedish * Esperanto * Dutch * Catalan * _More available on request_ * Battery bar display * Weather: Current conditions in °C or °F. * Night theme displayed between sunset and sunrise * Choose your weather provider: * **Yahoo** _No API key required at this moment_ * **Wunderground** * **OpenWeatherMap** * **Historical integration with Master Key (pmkey.xyz)** ## Future developments * Fuzzy time * Battery * Option for animations * Option for non-pmkey users * More languages (Swedish, Esperanto…) ## Attributions ### Fonts * Weather Icons by Eric Flowers, modified and fitted to the regular alphabet instead of Unicode values. * Custom font for icons created via Fontastic. * Gotham Fonts downloaded from fontsgeek.com ### Weather providers ### Others * Master Key (pmkey.xyz) was a service for Pebble users that managed API keys through a unique PIN. * wackyneighbor project DC TextWatch Deluxe ## Screenshots ## License Developed under license MIT. **Made in Madrid, Spain ❤️**

dieghernan.github.io

Do you know the interrupted Goode homolosine projection?

Let’s peel the world

https://fed.brid.gy/ What would happen if we peeled the Earth like an orange? The interrupted Goode homolosine projection gives us something close to that! This equal-area projection preserves relative areas while splitting the world map into lobes. Add a few shades of orange and our world is ready to peel. Let’s make it with **sf** , **giscoR** and **ggplot2**. First, we get the coastlines and draw the outline, following the cuts between the lobes. library(sf) library(ggplot2) library(giscoR) world <- gisco_get_coastal_lines(resolution = 10) goode_crs <- "+proj=igh" lat <- c(90:-90, rep(c(-90:0, 0:-90), 3), -90:90, 90:0, 0:90, 90) lon <- c( rep(180, 181), rep(c(80.01, 79.99, -19.99, -20.01, -99.99, -100.01), each = 91), rep(-180, 181), rep(c(-40.01, -39.99), each = 91), 180 ) outline <- st_sfc(st_polygon(list(cbind(lon, lat))), crs = 4326) |> st_transform(goode_crs) rectangle <- st_as_sfc(st_bbox(outline) * 1.1) mask <- st_difference(rectangle, outline) Next, we draw the meridians and parallels within each lobe so that they do not connect across the cuts. lobes <- list( c(-180, -40, 0, 90), c(-40, 180, 0, 90), c(-180, -100, -90, 0), c(-100, -20, -90, 0), c(-20, 80, -90, 0), c(80, 180, -90, 0) ) lines <- lapply(lobes, function(b) { west <- b[1] + 0.01 east <- b[2] - 0.01 south <- max(b[3], -89.99) north <- min(b[4], 89.99) meridians <- seq(-180, 180, 30) meridians <- meridians[meridians > west & meridians < east] parallels <- seq(-60, 60, 30) parallels <- parallels[parallels >= south & parallels <= north] c( lapply(meridians, function(x) { st_linestring(cbind(x, seq(south, north, length.out = 181))) }), lapply(parallels, function(y) { st_linestring(cbind(seq(west, east, length.out = 361), y)) }) ) }) grat <- st_sfc(do.call(c, lines), crs = 4326) |> st_transform(goode_crs) Finally, we add the orange palette. The mask hides anything outside the outline and keeps the edges of our peel tidy. ggplot() + geom_sf(data = outline, fill = "#FFF0D9", colour = NA) + geom_sf(data = world, fill = "#F9B566", colour = "#B34D08", linewidth = 0.25) + geom_sf(data = grat, fill = NA, colour = "#E8A45B", linewidth = 0.25) + geom_sf(data = mask, fill = "white", colour = NA) + geom_sf(data = outline, fill = NA, colour = "#E87516", linewidth = 0.4) + coord_sf( crs = goode_crs, xlim = st_bbox(outline)[c("xmin", "xmax")], ylim = st_bbox(outline)[c("ymin", "ymax")], expand = FALSE, datum = NA ) + theme_void() + theme( plot.background = element_rect(fill = "white", colour = NA), plot.title = element_text(color = "#E87516", face = "bold") ) + labs( title = "Let's peel the world", caption = "Interrupted Goode homolosine projection" ) ## References The outline and masking approach follows Claus O. Wilke’s Interrupted Goode homolosine example. For more on map projections, see Pyranhia’s Projections cartographiques : le guide pour ne pas y laisser sa santé mentale (in French).

dieghernan.github.io