libgd-gis: A Practical GIS Rendering Engine for Ruby

January 23, 2026

Raster maps, GeoJSON overlays, and real-world cartography — without leaving Ruby.

Over the last months, I’ve been working on libgd-gis, a GIS rendering engine built on top of libgd and designed specifically for Ruby developers who need static map generation without relying on browser-based toolchains or heavyweight GIS stacks.

This article walks through the motivation, architecture, and recent features — including native GeoJSON Point support — illustrated with real examples.

Article content

Why libgd-gis?

Ruby has excellent libraries for data processing, APIs, and background jobs — but when it comes to map rendering, the ecosystem is thin:

  • Browser-based stacks (Mapbox GL, Leaflet) require JS, tileservers, and headless rendering
  • Traditional GIS tools (GDAL, QGIS) are powerful but heavy and non-Ruby-native
  • Generating static images for reports, thumbnails, PDFs, or background jobs is surprisingly hard

libgd-gis was created to fill that gap:

A server-side, Ruby-native GIS engine for generating static maps using raster tiles and vector overlays.


Core Design Goals

  • Pure Ruby API
  • ✅ Raster basemaps (OSM, CARTO, ESRI, etc.)
  • ✅ GeoJSON overlays (lines, polygons, points)
  • ✅ Deterministic image output
  • ✅ No browser, no JS, no headless Chrome
  • ✅ Suitable for background jobs, reports, and automation

Advertise on RubyStackNews

RubyStackNews is a niche publication read by Ruby and Rails developers worldwide. Our audience includes senior engineers, tech leads, and decision-makers from the US, Europe, and Asia.

Sponsorship Options

📝 Article Sponsorship
Your brand featured inside a technical article (clearly marked as sponsored).
📌 Inline Sponsored Block
Highlighted sponsor section embedded within an article.
📎 Sidebar Sponsor
Logo + link displayed site-wide in the sidebar.
  • Highly targeted Ruby / Rails audience
  • Organic traffic from search and developer communities
  • No ad networks — direct sponsorships only

Interested in sponsoring RubyStackNews?

Contact

Rendering Model

libgd-gis follows a simple but powerful pipeline:

  1. Define a geographic bounding box (WGS84)
  2. Select a basemap provider
  3. Load vector layers (GeoJSON, points, CSV data)
  4. Render to a PNG image
  5. (Optional) draw post-render overlays (labels, legends, titles)
map = GD::GIS::Map.new(
bbox: [-74.02, 40.70, -73.93, 40.82],
zoom: 14,
basemap: :carto_light
)
map.style = GD::GIS::Style.load("default")
map.add_geojson("streets.geojson")
map.add_geojson("parks.geojson")
map.render
map.save("nyc.png")

Basemap Support

libgd-gis supports a wide range of tile providers out of the box:

  • OpenStreetMap (:osm)
  • CARTO (:carto_light, :carto_dark)
  • ESRI Satellite / Streets
  • Stamen (Toner, Terrain, Watercolor)
  • OpenTopoMap
  • Wikimedia
  • CyclOSM

Tiles are fetched, cached locally, and stitched into a single image using libgd.


Styles Are Mandatory (By Design)

One important design decision: a style is mandatory for rendering.

This keeps the rendering pipeline explicit and predictable. Styles define:

  • Layer order
  • Stroke and fill colors
  • Line widths
  • Text and label behavior

Styles are loaded from YAML and applied consistently across maps.

map.style = GD::GIS::Style.load("solarized")

This approach avoids “magic defaults” and makes map output reproducible.


GeoJSON Support (Lines, Polygons… and Now Points)

Article content

Originally, libgd-gis focused on line and polygon geometries (roads, rivers, parks, administrative boundaries).

✨ New: GeoJSON Point Support

Recent versions introduce native support for GeoJSON Point features.

This allows you to:

  • Render POIs directly from GeoJSON
  • Mix points, lines, and polygons in the same dataset
  • Attach icons and labels to point features

Example:

map.style = GD::GIS::Style.load("solarized")
map.add_points(
geojson["features"],
lon: ->(f) { f["geometry"]["coordinates"][0] },
lat: ->(f) { f["geometry"]["coordinates"][1] },
icon: nil,
label: ->(f) { f["properties"]["name"] },
font: "./fonts/DejaVuSans.ttf",
size: 12
)

styles/default.yml

...
points:
icon: null
font: "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
size: 12
color: [101, 123, 131]
...

This closes an important gap and makes libgd-gis suitable for:

  • City POI maps
  • Sensor locations
  • Infrastructure inventories
  • Data journalism visuals
Article content

Viewport Rendering (Fixed Image Sizes)

By default, maps are rendered using a tile-based model, where the final image size depends on the number of tiles.

libgd-gis also introduces an opt-in viewport mode:

map = GD::GIS::Map.new(
bbox: PARIS,
zoom: 13,
basemap: :carto_light,
width: 350,
height: 350
)

This allows:

  • Exact image sizes (thumbnails, avatars, video frames)
  • Deterministic output
  • Tile cropping instead of tile stitching

Tile-based mode remains the stable default.


Post-Render Overlays

Because everything is rendered into a GD::Image, you can draw directly on the result:

map.render
map.image.antialias = true
map.image.filled_rectangle(30, 30, 330, 100, bg)
map.image.text("PARIS", x: 60, y: 85, size: 36, font: font)

This makes it easy to add:

  • Titles
  • Legends
  • Watermarks
  • Annotations
Article content

Real-World Use Cases

libgd-gis is already being used for:

  • Automated report generation
  • Infrastructure and asset maps
  • GIS thumbnails in background jobs
  • Data exploration and visualization
  • Static maps for PDFs and emails

All without leaving Ruby.


Project Status

  • Language: Ruby
  • Rendering: libgd
  • Current version: 0.2.x
  • Status: Actively evolving
  • GeoJSON: Lines, polygons, and points supported

Feedback, issues, and contributions are very welcome.

👉 GitHub: https://github.com/ggerman/libgd-gis 👉 Issues: https://github.com/ggerman/libgd-gis/issues


Final Thoughts

libgd-gis doesn’t try to replace full GIS stacks or interactive web maps.

Instead, it focuses on a clear niche:

Generate high-quality static maps from Ruby — reliably, deterministically, and without browser dependencies.

If you’ve ever needed a map inside a background job, a PDF report, or a batch process, this tool might be exactly what you were missing.

Article content

Leave a comment