Rendering Incremental Points on Maps with Ruby and Rails

Rendering Incremental Points on Maps with Ruby and Rails
Rendering Incremental Points on Maps with Ruby and Rails

January 30, 2026

When working with maps in Ruby or Ruby on Rails applications, most solutions assume that all geospatial data must be prepared upfront — usually as GeoJSON layers. While this works well for static datasets, it becomes inefficient when dealing with event-driven data such as sales, visits, calls, or real-time activity.

In many Rails applications, new coordinates arrive one by one through an API. Rebuilding or reprocessing a full GeoJSON layer for each update is unnecessary overhead.

To address this, I added a new API to libgd-gis: add_point.


Why add_point?

Article content

The goal of add_point is simple:

Render individual, customizable points on a map — incrementally — without rebuilding complex layers.

Instead of thinking in terms of datasets, add_point allows you to think in terms of events.

Each point can be rendered independently and styled at render time.


What can a point represent?

With add_point, each point can be rendered as:

  • an image icon (PNG, JPG, etc.)
  • a numeric marker: 1, 2, 3, …
  • an alphabetic marker: a, b, c, …
  • a symbol marker: *, +, -, /, (

This makes it suitable for many real-world use cases:

  • sales appearing on a global dashboard
  • incoming API requests
  • phone calls or tickets
  • live visits per city
  • monitoring or observability maps

All rendered directly from Ruby, without external rendering engines.


Styling points with YAML

Point styling integrates with the existing style system.

Example (styles/solarized.yml):

global:
label:
color: [35, 35, 35, 20]
point:
font_color: [255, 85, 0]
color: [0, 43, 54, 100]
font: /usr/share/fonts/truetype/lato/Lato-Regular.ttf
size: 12
icon: numeric

This allows consistent styling across the map while still letting each point override options when needed.


A practical example

Below is a fully working example that renders a world map and incrementally adds points with different marker types.

This example mixes:

  • icon-based landmarks
  • numeric markers
  • alphabetic markers

Setup

require "gd/gis"
require "date"
require_relative "fonts" # helper that picks a random system font

Drawing a simple legend

Article content
def draw_legend(img)
padding = 10
x = 20
y = 20
w = 420
h = 70
bg = GD::Color.rgba(0,0,0,180)
border = GD::Color.rgb(255,255,255)
text1 = GD::Color.rgb(255,255,255)
text2 = GD::Color.rgb(180,220,255)
img.filled_rectangle(x, y, x+w, y+h, bg)
img.rectangle(x, y, x+w, y+h, border)
img.text(
"libgd-gis v0.3.2",
x: x + padding,
y: y + 28,
size: 20,
color: text1,
font: GD::Fonts.random
)
img.text(
"Maps & cartography rendering for Ruby",
x: x + padding,
y: y + 52,
size: 14,
color: text2,
font: GD::Fonts.random
)
end

Creating the map

WORLD_BBOX = [-179.999, -85.0511, 179.999, 85.0511]
OUTPUT = "output/point.png"
map = GD::GIS::Map.new(
bbox: WORLD_BBOX,
zoom: 3,
basemap: :carto_dark
)
map.style = GD::GIS::Style.load("solarized")

Incrementally adding points

cities = [
{ lon: -58.3816, lat: -34.6037, name: "Buenos Aires" },
{ lon: -56.1645, lat: -34.9011, name: "Montevideo" },
{ lon: -70.6693, lat: -33.4489, name: "Santiago" },
{ lon: -74.0060, lat: 40.7128, name: "New York", icon: "icons/statue_of_liberty_new_york.png" },
{ lon: -43.1729, lat: -22.9068, name: "Rio de Janeiro", icon: "icons/christ_redeemer_rio.png" },
{ lon: -0.1276, lat: 51.5074, name: "London", icon: "icons/big_ben_london.png" },
{ lon: 2.3522, lat: 48.8566, name: "Paris", icon: "icons/eiffel_tower_paris.png" },
{ lon: 12.4964, lat: 41.9028, name: "Rome", icon: "icons/colosseum_rome.png" }
]
cities.each_with_index do |p, index|
if p[:icon]
map.add_point(
lon: p[:lon],
lat: p[:lat],
label: p[:name],
icon: p[:icon]
)
else
if [true, false].sample
map.add_point(
lon: p[:lon],
lat: p[:lat],
label: p[:name],
symbol: index
)
else
map.add_point(
lon: p[:lon],
lat: p[:lat],
label: p[:name],
icon: "alphabetic",
color: [6, 117, 196],
font_color: [250, 250, 250],
symbol: index
)
end
end
end

Rendering

Article content
map.render
img = map.image
draw_legend(img)
img.save(OUTPUT)
puts "✔ Generated: #{OUTPUT}"

Why this fits Rails well

This approach maps naturally to Rails applications:

  • each API request can call add_point
  • no GeoJSON regeneration
  • no tile servers
  • no JavaScript rendering
  • deterministic server-side output

It is especially useful for dashboards, reports, monitoring tools, or scheduled image generation.


Final thoughts

add_point is not about replacing GeoJSON. It is about complementing it with a simpler, event-oriented rendering model.

Sometimes, a map is just a list of points — and Ruby should be able to render that efficiently.

Article content

Leave a comment