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:
|
|
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:
|
|
To add overlays, we extend the call with a block:
|
|
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 m — m.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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
The scale control defaults to metric units. For US-shaped apps where imperial is more natural, override:
|
|
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:
|
|
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:
|
|
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.controlDSL method and thezoom_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.