flowchart TD
A["🚀 Push to main<br>(or workflow_dispatch)"] --> B["📦 Checkout Repository<br>(actions/checkout@v4)"]
B --> C["🔧 Install & Cache System Deps<br>(cache-apt-pkgs-action)<br>GDAL · GEOS · libmagick++ · HarfBuzz"]
C --> D["🐍 Setup Python<br>(micromamba)<br>pyenv/base/environment.yml"]
C --> E["📊 Setup R 4.5.0<br>(r-lib/actions/setup-r)<br>renv restore · armyknives profile"]
D --> F["⚙️ Setup Quarto<br>(quarto-dev/quarto-actions/setup)"]
E --> F
F --> G["📝 Render & Publish<br>(quarto-dev/quarto-actions/publish)<br>Target: gh-pages branch"]
G --> H["❄️ Commit _freeze/ back to main<br>(bot commit)"]
H --> I["🌐 Live on GitHub Pages<br>yonghuni.github.io/blog"]
style A fill:#4CAF50,color:#fff
style I fill:#2196F3,color:#fff
style H fill:#FF9800,color:#fff
A CI/CD Enabled Computational Geo-Blog
This blog is a platform for sharing reproducible geospatial research, computational methods, and practical coding workflows. Every post is rendered and deployed automatically through GitHub Actions, so the content you see is always built from the latest source.
What is Quarto?
Quarto is an open-source scientific publishing system developed by Posit, and it represents the next evolution of R Markdown. While R Markdown was tightly coupled to the R ecosystem, Quarto breaks that boundary by natively supporting R, Python, Julia, and Observable JS within a single document. This means a geographer can wrangle spatial data in R with sf, run a machine learning model in Python with scikit-learn, and present the results, all without leaving one .qmd file.
What makes Quarto particularly powerful for research is its commitment to reproducibility. The traditional workflow of exporting figures from GIS software and pasting them into a Word document is replaced by a single source of truth: if the underlying data changes, every map, table, and chart in the document updates automatically. Because all code is embedded alongside the narrative, fellow researchers can trace and reproduce the entire analytical pipeline.
Quarto also follows a “one source, multi-format” philosophy. A single .qmd file can be rendered into an HTML report, a PDF manuscript formatted for journal submission, or an MS Word document for collaboration, simply by changing one line in the YAML header. It even supports presentation slides via Reveal.js. And for quantitative work, LaTeX math rendering comes built-in; for instance, the formula for Moran’s I, a measure of spatial autocorrelation, renders at publication quality:
\[I = \frac{n}{\sum_{i=1}^{n}\sum_{j=1}^{n}w_{ij}} \frac{\sum_{i=1}^{n}\sum_{j=1}^{n}w_{ij}(z_i-\bar{z})(z_j-\bar{z})}{\sum_{i=1}^{n}(z_i-\bar{z})^2}\]
By linking with GitHub Pages, researchers can publish their own blogs or project pages to the world with just a few clicks. This blog is a living example of exactly that.
Features of This Blog
Multi-Language Interoperability
Nearly every post on this blog passes data between R and Python within a single document using the reticulate package. R excels at spatial data handling and publication-quality visualization (sf, tmap, ggplot2), while Python offers optimized numerical computing and machine learning (NumPy, scikit-learn, SciPy). By combining both, each step in the analysis uses the best tool for the job.
In practice, this means a post can parse KMA radar binary data in Python, convert reflectance to rainfall intensity via the Z-R relationship, and then visualize the resulting raster in R with interactive tmap basemaps, all in one .qmd file. Another post computes OLS via the normal equation \((X^TX)^{-1}X^Ty\) in Python, optimizes the MLE with SciPy, and cross-checks both against R’s built-in lm().
Interactive Geospatial Visualization
Static maps can only tell part of the story. This blog makes heavy use of interactive mapping with tmap in view mode, leafem for mouse coordinates, and custom Google Satellite and OpenStreetMap basemaps, so readers can zoom, pan, toggle layers, and click on features to inspect attributes directly in the browser. For client-side interactivity, Observable JavaScript (OJS) receives data processed by R during the build phase via ojs_define(), enabling real-time filtering and plotting with no running server.
CI/CD Pipeline
The entire build-and-deploy process is defined in a single GitHub Actions workflow (.github/workflows/publish.yml). Every push to main (or a manual workflow_dispatch) triggers the following pipeline:
Both the Python environment (resolved from pyenv/base/environment.yml) and the system-level packages are cached between runs, so builds only pay the full install cost once. R packages are restored through renv with the "armyknives" profile, pinning the exact versions used locally. The final _freeze/ commit ensures expensive computations are never re-run unless their source code changes; if nothing has changed, the bot commit is skipped gracefully.
Theming and Customization
The site uses the Cosmo Bootstrap theme with a custom dark mode defined in misc/theme-dark.scss. A prefers-color-scheme media query in misc/styles.css handles light-mode footer styling. The page footer dynamically injects the Quarto version and build OS through a custom Lua filter (misc/quarto-version.lua), and icons for ORCID and academic profiles come from the fontawesome and academicons extensions.
Tech Stack
| Component | Technology |
|---|---|
| Static Site Generator | Quarto |
| Languages | R, Python, Julia, Observable JS |
| Geospatial | sf, terra, GeoPandas, GDAL, tmap, leafem, folium |
| Data Science | scikit-learn, data.table, NumPy, SciPy, Arrow |
| Visualization | ggplot2, tmap, matplotlib, Observable Plot |
| Hosting | GitHub Pages (via gh-pages branch) |
| CI/CD | GitHub Actions |
| R Environment | renv ("armyknives" profile) |
| Python Environment | Micromamba (pyenv/base/environment.yml) |
| Theme | Cosmo + custom dark SCSS |
| Comments | Giscus (GitHub Discussions) |
| Extensions | FontAwesome, Academicons |
| Custom Filters | Lua filter for Quarto version injection |
Comments
Every post includes a Giscus comment widget backed by GitHub Discussions, injected via shared metadata in
posts/_metadata.yml. The widget’s theme automatically syncs with the site’s light/dark toggle.