Skip to content

Lesson 5 — The overlay convention

The map you built in Lesson 3 has Australia on the screen but no controls beyond mouse-wheel zoom. There’s no scale bar to orient distances. No clear way to zoom in and out without scrolling. The attribution exists, but it’s tucked into a popout that the reader has to discover.

This lesson adds a standard set of overlays — navigation, scale, a zoom indicator — and explains the convention that determines where they live. By the end the Lab map looks like a real piece of UI rather than a stub.

Overlays live at the corners

A map’s overlays are the small pieces of UI that float on top of the map surface — controls, indicators, legends. There are four corners, and each is a natural home for a different category of overlay:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  ┌────────────────────────────────────────────┐
  │ top-left                          top-right│
  │   navigation                        legends│
  │   geolocate                  & overlay UI  │
  │   fullscreen                               │
  │   drawing tools                            │
  │                                            │
  │                                            │
  │ bottom-left                    bottom-right│
  │   scale bar                     attribution|
  │   zoom indicator              & annotations│
  └────────────────────────────────────────────┘

The convention is mostly empirical — it’s where these things have ended up across enough mapping software that users now expect them there. Some specific reasons:

  • Top-left is for navigation. The user’s hand is usually at the keyboard or near the top of the screen; controls there are reachable. Most mapping libraries default navigation to top-left, and most apps don’t override it.
  • Top-right is for overlay UI — legends, layer toggles, coloured-key panels. These often summarise what the data on the map means; placing them out of the way of navigation keeps both visible without overlap.
  • Bottom-left is for scale and zoom indicators. They inform but don’t need clicking; tucking them into a corner out of the navigation flow makes sense.
  • Bottom-right is for attribution and annotations. Attribution is required by tile providers’ terms of service. It’s small, low-priority for the user, but legally important. Bottom-right is where MapLibre places it by default and where users have learned to look for it.

The gem encodes these defaults. When you add an overlay via m.control (or via the zoom_indicator: option on Vera::Map.new), you don’t need to specify a position; it lands in the conventional corner unless you say otherwise.

What’s already there

Before adding anything, take a moment to look at what the existing Lab map already shows.

Activity 1 — Find the existing attribution

Look at the bottom-right corner of the Lab map. You should see a small i icon. Click it.

A popout appears showing attribution — something like © CARTO © OpenStreetMap contributors. This is MapLibre’s default attribution control; it’s added automatically without any explicit configuration on your part.

Why is it there even though you never added it? MapLibre adds it by default because tile providers’ terms of service require attribution to be visible. The collapsed-popout form keeps the visual real estate small while still being legally compliant. You can opt out only by configuring MapLibre explicitly, which the gem doesn’t expose because removing attribution would violate the providers’ terms.

So one of the four corners (bottom-right) is already populated. The other three are empty.

Adding navigation and scale

Let’s add navigation and scale controls to the Lab map. The m.control method is part of the gem’s DSL, accessed through a block on Vera::Map.new.

Open app/views/lab/index.html.erb. The map is currently rendered like this:

1
2
3
4
<%= render Vera::Map.new(
      id:     "lab-map",
      height: "600px"
    ) %>

To add overlays, we extend the call with a block:

1
2
3
4
5
6
7
<%= render Vera::Map.new(
      id:     "lab-map",
      height: "600px"
    ) do |m|
  m.control :navigation
  m.control :scale
end %>

This is the first time in the tutorial that you’ve seen a Phlex component invoked with a block in ERB. The do |m| ... end syntax passes a block to Vera::Map.new, and inside the block, m is a reference to the map being configured. The DSL methods on mm.control, m.source, m.layer, and the rest — populate the map’s configuration.

The whole call is wrapped in a single <%= ... %> because the expression as a whole — render plus its block — produces the HTML that gets inserted into the page. Inside the block, the DSL calls are just normal Ruby and don’t need separate ERB tags around each one.

Activity 2 — Add the navigation and scale overlays

Update your Lab page’s render call to add the two overlays. Reload the page.

You should see:

  • A pair of buttons near the top-left of the map: a + for zoom in, a for zoom out. That’s the navigation control.
  • A small line near the bottom-left showing distance scale. Pan and zoom; watch how the scale updates as the visible area changes.

Combined with the attribution that was already in the bottom-right, three of the four corners are now populated. The map is starting to feel finished.

Adding the zoom indicator

There’s a fourth overlay worth adding to the Lab map: a zoom indicator showing the current zoom level numerically. It’s useful during development — you can see at a glance whether you’re at zoom 8 or zoom 12, which matters when you’re diagnosing why a layer isn’t appearing or a query is loading the wrong amount of data.

The zoom indicator is a special case in the gem. It’s not a MapLibre control; it’s a small piece of HTML the gem renders inside the map div. So instead of m.control :zoom_indicator, you enable it via a top-level option on Vera::Map.new:

1
2
3
4
5
6
7
8
<%= render Vera::Map.new(
      id:             "lab-map",
      height:         "600px",
      zoom_indicator: true
    ) do |m|
  m.control :navigation
  m.control :scale
end %>

Passing zoom_indicator: true enables it at the default position (bottom-left), where it appears alongside the scale bar.

You can pass an explicit position too:

1
zoom_indicator: :top_right

Valid positions are :top_left, :top_right, :bottom_left, :bottom_right. Passing false or omitting the option disables it.

Activity 3 — Add the zoom indicator

Update the Lab page to enable the zoom indicator alongside the other overlays:

1
2
3
4
5
6
7
8
<%= render Vera::Map.new(
      id:             "lab-map",
      height:         "600px",
      zoom_indicator: true
    ) do |m|
  m.control :navigation
  m.control :scale
end %>

Reload. You should see a small zoom value (probably “z 4”) appear near the bottom-left of the map, alongside the scale. Click + on the navigation control or scroll to zoom in; the zoom number updates in real time.

Pan and zoom around for a moment. You’ll find it surprisingly useful for understanding zoom levels intuitively — at z 4 you see continents; at z 10 you see metropolitan areas; at z 14 individual streets become legible.

Why no positions?

You’ll have noticed that the m.control calls didn’t specify where each overlay should go. The gem placed each in its conventional corner: navigation top-left, scale bottom-left. The zoom indicator landed in the bottom-left too because that’s its default.

You can override per call if you have a reason:

1
2
m.control :navigation, position: :top_right
m.control :scale,      position: :bottom_right

But you usually shouldn’t. Consistency across maps in the same application is more valuable than micro-optimising the layout of any individual map. Override only when there’s a reason — for example, if the navigation control would obscure important content in the top-left of a particular page.

The fourth corner

Three of the four corners are now populated by the standard overlays: navigation top-left, scale and zoom indicator bottom-left, attribution bottom-right.

The top-right is by convention reserved for legends and overlay UI — colour keys, layer toggles, status panels. The gem doesn’t ship a built-in legend control because legends are domain-specific. The legend for a job-density choropleth shows colour bins and what they mean (“0–10 jobs”, “11–50”, “51–200”, “200+”); the legend for a technician-status overlay shows different colours and a different scale. Each application builds its own.

We’ll build legend components in Module 6 when the choropleth chapter arrives. For now, just be aware that the top-right corner is reserved territory and don’t put navigation or scale there without a reason.

A few control options worth knowing

The navigation control has a few options worth knowing about.

By default, the gem hides MapLibre’s compass — the round button that resets rotation. For 2D data maps, rotation is rarely used, and the compass button mostly sits there looking like a broken control. If you want it back, opt in:

1
m.control :navigation, compass: true

The scale control defaults to metric units. For US-shaped apps where imperial is more natural, override:

1
m.control :scale, unit: :imperial

The geolocate control adds a button that locates the user on the map (using the browser’s Geolocation API, which prompts the user for permission). Common in field-officer-facing maps where “where am I right now?” is a real question:

1
m.control :geolocate

By default this lands in the top-left, alongside navigation.

The fullscreen control adds a button that expands the map to fill the screen — useful on dashboards where a small map can become a full-screen detail view temporarily. Also defaults to top-left:

1
m.control :fullscreen

We don’t need any of these on the Lab page right now. They’re mentioned so you know they exist when you reach for them in later modules.

Activity 4 — Try a few overlay variations

Experiment with the gem’s overlay options. Try:

  • Adding the compass to the navigation control: m.control :navigation, compass: true. A new round button appears at the top-left.
  • Adding the geolocate control: m.control :geolocate. Click the button it adds; the browser will ask for permission to share your location.
  • Switching the scale to imperial: m.control :scale, unit: :imperial. The scale shows miles instead of kilometres.
  • Moving the zoom indicator: zoom_indicator: :top_right.

When you’re done, return the configuration to navigation, scale, and the bottom-left zoom indicator so the Lab is in a clean state for Module 3.

What we have now

By the end of Module 2, your Lab page renders:

  • A 600-pixel-tall map of Australia, positron-styled
  • Navigation controls in the top-left
  • A scale bar and zoom indicator in the bottom-left
  • Attribution in the bottom-right (added automatically by MapLibre)
  • The dispatch deck’s chrome (top bar, sidebar, page header) surrounding it

The map is empty of data so far. There are no markers, no points, no polygons — just the basemap. That’s deliberate; the spatial data work begins in Module 3 with PostGIS.

You also have:

  • A clear sense of how the gem’s Phlex component is invoked from an ERB view (<%= render Vera::Map.new(...) do |m| ... end %>)
  • An understanding of the overlay convention and the gem’s defaults
  • Familiarity with the m.control DSL method and the zoom_indicator: option

One pattern worth signposting

The Lab map’s configuration is small enough to live inline in the ERB. As the tutorial progresses, our maps will grow more elaborate — multiple sources, layered fills and outlines, popups, click handlers, paint expressions driven by data. At some point the inline block becomes too much weight for the page that contains it.

When that happens, the right move is to extract the whole Vera::Map declaration into a Phlex component in app/components/. The page’s ERB simplifies to a single render call, and the map’s complexity lives in a class that can be named, tested, and reused. We’ll see this transition for the first time in Module 4 when our maps start showing real data. For now, inline blocks in the ERB work fine.

Where this leaves us

Module 2 is complete. You’ve installed PostgreSQL with PostGIS, cloned the dispatch deck chassis, learned what Vera contributes to the chassis, rendered your first map, internalised the coordinate ordering convention, and added the standard overlays.

Module 3 is the first deep PostGIS module. We add a Depot model with a geometry column, insert a real point of interest into the database, and query it back. We import the Australian Bureau of Statistics SA3 boundaries — about 360 polygons that become the foundation for service areas — via a rake task. By the end of Module 3 the database is populated with real spatial data, ready for Module 4 to render it on the map.

The slow setup is behind us. The interesting work begins.