Chapter 5: Map Layers#
Welcome back! In the previous chapters, we've set up our map's "window" (Chapter 1: Map Instance), learned about the master blueprint (Chapter 2: Map Style), discovered where to find our geographic data (Chapter 3: Data Sources), and even figured out how to efficiently get that data from a single file using the Chapter 4: PMTiles Protocol.
So now we have the map instance ready, the style recipe defined, the ingredients (data sources) listed, and a way to get the ingredients (PMTiles protocol). But if you ran the code with just sources, you wouldn't see anything on the map yet! Why?
Think about our collage analogy again. You have your collage board (Map Instance), you have your plan (Map Style), and you have your box of photos and paper (Data Sources). But you haven't actually pasted anything down yet! You need instructions like "Take a photo of the Eiffel Tower and glue it here," or "Cut out blue paper and make it look like a river running across the page."
In web mapping, these "pasting and drawing instructions" are called Map Layers.
What are Map Layers?#
Map Layers define how data from a source is actually drawn, or rendered, onto the map canvas.
They are the visual representation rules. While a data source provides the raw geographic information (like the shape of a river), a layer tells MapLibre GL JS:
- "Okay, take that river data..."
- "...and draw it as a blue line..."
- "...make the line 2 pixels wide..."
- "...and only show it when the user is zoomed in close enough."
A single data source can be used by multiple layers to display different aspects of the data or the same data in different ways. For example, you might have one vector source with road data. You could have one layer that draws major highways as thick red lines, and another layer that draws smaller streets as thin gray lines, both using the same source data but with different layer definitions.
Our Goal: Understand How Layers Draw Data#
Our goal for this chapter is to understand what a layer object looks like in the layers
section of the map style and how its properties tell MapLibre GL JS what to draw, which data source to use, and how it should appear visually.
How Layers are Defined#
Map layers are defined in the layers
section of the map style object. As we saw briefly in Chapter 2: Map Style, this section is an array ([...]
) of layer objects.
Here's where the layers
array fits within the style:
const map = new maplibregl.Map({
container: "map",
style: {
version: 8,
sources: {
// ... data source definitions here ...
},
layers: [ // <-- This is the layers array
{ // <-- This is one layer object
id: "...",
type: "...",
source: "...",
// ... other layer properties ...
},
{ // <-- This is another layer object
id: "...",
type: "...",
source: "...",
// ... other layer properties ...
},
// ... and so on for all layers ...
],
// ... other style properties ...
},
// ... rest of map config ...
});
Each object within the layers
array represents one visual layer on the map. The order of the objects in this array matters because layers are drawn from bottom to top. The first layer in the array is drawn first, then the second layer is drawn on top of the first, and so on.
Let's look at the key properties of a layer object using simplified examples from our project:
1. id
(Required)#
{
id: "usa-boundary-fill", // A unique name for this layer
// ... other properties ...
}
id
. This is how you refer to the layer later if you want to dynamically change its visibility, style, or other properties using MapLibre GL JS functions. Choose descriptive names!
2. type
(Required)#
{
id: "some-layer",
type: "fill", // <-- What kind of drawing?
// ... rest of layer ...
}
type
property tells MapLibre how to draw the data. Common types include:
* fill
: For drawing polygons as filled shapes (like countries, lakes, building footprints).
* line
: For drawing lines (like roads, rivers, boundaries).
* symbol
: For drawing point data, often with icons or text labels (like city names, points of interest).
* raster
: For displaying raster images (like satellite imagery or hillshades).
* circle
: For drawing points as circles.
* raster-dem
: Not a layer type, but a source type for terrain (covered in the next chapter).
The type determines what paint
and layout
properties are available.
3. source
(Required)#
{
id: "ky-streams-line",
type: "line",
source: "ky-streams", // <-- Use the source named "ky-streams"
// ... rest of layer ...
}
source
property links this layer to one of the data sources defined in the sources
section of the style (see Chapter 3: Data Sources). The value of source
must match the name (key
) of a source defined earlier in the style object.
4. source-layer
(Required for Vector Sources)#
{
id: "ky-streams-line",
type: "line",
source: "ky-streams",
"source-layer": "ky_strm_line", // <-- Use features from the "ky_strm_line" collection within the source
// ... rest of layer ...
}
source-layer
property (note the hyphen, it needs quotes in JSON/JS objects) specifies which specific collection of features from the linked source
this layer should use. This property is only applicable when source.type
is "vector"
.
5. paint
(Optional but Common)#
{
id: "usa-boundary-fill",
type: "fill",
source: "usa-pmtiles",
"source-layer": "usa",
paint: { // <-- How does it look?
"fill-color": "#444", // Dark gray fill
"fill-opacity": 0.7, // Slightly transparent
},
// ... rest of layer ...
}
paint
property is an object that contains visual styling properties. These properties are specific to the layer type
. For a fill
layer, you might set fill-color
or fill-opacity
. For a line
layer, you'd set line-color
or line-width
. These properties often change based on zoom level or data attributes using expressions.
6. layout
(Optional)#
{
id: "ky-streams-line",
type: "line",
source: "ky-streams",
"source-layer": "ky_strm_line",
layout: { // <-- How does it behave/look non-visually?
visibility: "visible", // Can be "none" to hide
"line-cap": "round", // Style the line ends
},
// ... rest of layer ...
}
layout
property is an object for properties that affect the visual appearance but don't involve colors, widths, or opacity directly. This includes things like visibility
, line-cap
, line-join
, or text properties for symbol
layers (like text-field
or text-anchor
). Like paint
properties, they can also use expressions.
7. filter
(Optional)#
{
id: "ky-streams-filtered",
type: "line",
source: "ky-streams",
"source-layer": "ky_strm_line",
filter: ["==", "ftype", 460], // <-- Only show features where 'ftype' property is 460
// ... paint/layout ...
}
filter
property is an array that defines rules for which features from the source-layer
should actually be drawn by this layer. It's a powerful way to show only a subset of the data based on its attributes (properties). In the example above, this layer would only draw streams that have an attribute named ftype
with a value of 460
.
Putting Layers Together in Our Project#
Let's look at a few layer definitions from our map/main.js
file, now that we understand the basic properties.
First, the usa-pmtiles
source is used by a fill
layer to draw the USA boundary:
// Inside the layers array [...]
{
id: "usa-pmtiles", // Unique name for the layer
type: "fill", // Draw as a filled polygon
source: "usa-pmtiles", // Use the source defined as "usa-pmtiles"
"source-layer": "usa", // Use the "usa" collection from that source
paint: { // Visual styling
"fill-color": "#444", // Dark gray color
},
},
Next, let's look at a line
layer for the Kentucky streams:
// Inside the layers array [...]
{
id: "ky-streams", // Layer name
type: "line", // Draw as lines
source: "ky-streams", // Use the source "ky-streams"
"source-layer": "ky_strm_line", // Use the "ky_strm_line" collection
filter: ["any", ["in", "ftype", 460, 558]], // Only show features where ftype is 460 or 558
layout: { // Layout properties
visibility: "visible",
"line-cap": "round",
"line-join": "round",
},
paint: { // Paint properties
"line-color": "#4fa9e9", // Blue color
"line-width": 2, // 2 pixels wide
"line-opacity": [ // Opacity changes with zoom!
"interpolate", ["linear"], ["zoom"],
12, 0, // At zoom 12, opacity is 0 (invisible)
13, 1 // At zoom 13, opacity is 1 (fully visible)
],
},
},
"ky-streams"
vector source and the "ky_strm_line"
source-layer.
* The filter
makes sure only specific types of streams (those with ftype
460 or 558) are drawn by this layer.
* layout
sets some non-visual line styles.
* paint
sets the color and width, but also uses an interpolate
expression for line-opacity
. This expression tells MapLibre to smoothly change the opacity as the map zooms between level 12 (where it's fully transparent, 0) and level 13 (where it's fully opaque, 1). This creates a fade-in effect for the streams as you zoom closer.
Finally, let's see a raster
layer for the hillshade image:
// Inside the layers array [...]
{
id: "hillshade", // Layer name
type: "raster", // Draw as a raster image
source: "hillshade", // Use the source named "hillshade"
// Raster layers don't have source-layer
// paint and layout properties are specific to raster type
paint: {
// ... raster paint properties would go here ...
}
},
"hillshade"
source. Raster layers don't have a source-layer
because the source itself is typically just one image or set of image tiles.
You can see all these layer definitions and more in the layers
array within the style object in our map/main.js
file.
Order of Layers#
Remember that layers are drawn in the order they appear in the layers
array. In our project's style, the hillshade
layer appears after the usa-pmtiles
layer. This means the hillshade image is drawn on top of the dark gray USA boundary fill. The stream and waterbody layers appear even later in the list, so they are drawn on top of both the USA boundary and the hillshade. This is how you control which features appear visually dominant or whether one layer obscures another.
Under the Hood: MapLibre Processing Layers#
When you create the Map Instance
with the style
object, MapLibre GL JS reads the layers
array along with the sources
.
Here's a simplified flow of what happens when MapLibre needs to display a certain area at a certain zoom level:
sequenceDiagram
participant MapLibre as MapLibre GL JS
participant StyleObject as Map Style Object
participant DataSources as Data Sources
participant LayersArray as Layers Array (in style)
participant DrawingEngine as Drawing Engine
MapLibre->StyleObject: Read the 'style'
MapLibre->MapLibre: Determine visible area and zoom
MapLibre->LayersArray: Iterate through each layer definition (bottom to top)
loop For each Layer
MapLibre->MapLibre: Check layer's visibility and zoom constraints
alt If layer is visible at this zoom
MapLibre->LayersArray: Get layer's 'source' and 'source-layer'
MapLibre->DataSources: Request necessary data tiles for source + visible area
DataSources-->MapLibre: Provide data tiles (using PMTiles handler if needed)
MapLibre->MapLibre: Process incoming data tiles
MapLibre->LayersArray: Apply layer's 'filter' to data features
MapLibre->DrawingEngine: Pass filtered data features and layer's 'type', 'paint', 'layout' rules
DrawingEngine->MapLibre: Draw features on the canvas
end
end
MapLibre->MapLibre: Display updated canvas in browser
For each layer, MapLibre figures out which data it needs from the specified source
and source-layer
for the current view. It fetches that data (requesting tiles as needed, perhaps via the PMTiles handler). Then, for each feature in the received data tiles, it checks if the layer's filter
allows it to be drawn. Finally, it uses the layer's type
, paint
, and layout
properties to draw the eligible features onto the canvas in the correct visual style, stacking layers according to their order in the array.
This process is repeated efficiently as you pan or zoom, ensuring that the correct data is fetched and redrawn according to the layer rules for the updated view.
Conclusion#
In this chapter, we learned that Map Layers are the instructions within the Map Style's layers
array that tell MapLibre GL JS how to draw data from the defined sources. We saw how layers link to sources using the source
and source-layer
properties and define visual appearance and behavior using type
, paint
, and layout
. We also learned how filter
can be used to draw only specific features and that the order of layers in the array determines the drawing order.
Layers are the key to making your data visible and creating the visual look of your map!
With our map instance set up, sources defined, PMTiles protocol enabled, and layers drawing basic features, we're ready to add one of the most exciting features: 3D terrain.
In the next chapter, we'll explore Chapter 6: Terrain and see how a special type of source and a simple map setting can turn our flat map into a dynamic 3D landscape.
Generated by AI Codebase Knowledge Builder