Skip to content

Crosshatch

A Mapzen classic. Lower Manhattan as a pen-and-ink drawing, after the crosshatch style Patricio Gonzalez Vivo wrote for Mapzen’s Tangram in 2015. Ground, parks and water use Tangram’s own hatch and paper textures. Buildings are hatched extrusions, and labels are set in a serif italic.

This page is the static tier of the classic. It is one YAML document, plain style spec from top to bottom: no page JavaScript, no shader, no runtime beyond MapLibre itself. As a result the document is its own export: mlym emit --strict compiles it with zero warnings. Nothing is declared absent, nothing is lossy, and the emitted style.json renders the same map in vanilla MapLibre.

What it reproduces exactly, and what it approximates:

  • Exact: ground, landuse and water. Tangram draws these unlit: a hatch texture repeated per tile, mixed from ink to paper, and a crumpled-paper normal map under fixed lights. Each one bakes to a plain image, and a *-pattern repeats that image just as Tangram did.
  • Approximate: buildings. In Tangram the buildings are the lit part, with stroke density following the light on each face. Here every face gets the same light pen hatch, and the document’s light: shades the faces. Tangram’s height exaggeration (×2.25 at z13 down to ×1 at z18) is kept, as one interpolate.

The tonal hatching on building faces is the job of the experimental tonal-hatch effect: a MapLibre custom layer that keeps this document as its fallback and upgrades the buildings layer in place. Where the effect isn’t loaded, and in every exported style, this page’s look is what renders. The Mapzen classics page shows both side by side.

crosshatch.yaml
# yaml-language-server: $schema=https://docs.maplibre-yaml.org/schema/latest/map.schema.json
# Mapzen classic: crosshatch — after Tangram's crosshatch style
# (tangrams/tangram-sandbox styles/crosshatch.yaml, @patriciogv 2015, MIT).
#
# The static tier: every layer is plain style spec, so this document is its
# own export — `mlym emit --strict` compiles it with nothing lost. Ground,
# landuse and water reproduce Tangram exactly (Tangram draws them unlit:
# tile-repeated hatch textures and a pre-lit crumpled-paper water, baked by
# `mlym bake crosshatch`). Buildings are the one lit layer: here every face
# gets the same light pen hatch, shaded by `light:`. Tangram's tonal
# hatching — stroke density following light per face — is the runtime
# effect's job, not this document's.
#
# Image and glyph URLs are absolute so emit can fetch them into the sprite
# and the exported style can find its fonts.
type: map
id: crosshatch
config:
center: [-74.0118, 40.7075]
zoom: 15.9
pitch: 60
bearing: 29
# An inline base style that carries only the self-hosted serif italic
# glyphs (Libre Baskerville Italic, OFL — Tangram labelled in Baskerville
# italic). The document owns every layer.
mapStyle:
version: 8
glyphs: "https://docs.maplibre-yaml.org/fonts/{fontstack}/{range}.pbf"
sources: {}
layers: []
# Building shading. Tangram's lights are fixed to the world, so this one is
# anchored to the map: from the south-west (239°), 30° from overhead — the
# same light MapLibre's default gives at this camera's bearing, now staying
# put as the map turns. Tune `position`/`intensity` to change face contrast.
light:
anchor: map
position: [1.15, 239, 30]
intensity: 0.5
images:
earth: { url: "https://docs.maplibre-yaml.org/classics/crosshatch/earth.png", pixelRatio: 2 }
landuse: { url: "https://docs.maplibre-yaml.org/classics/crosshatch/landuse.png", pixelRatio: 2 }
water: { url: "https://docs.maplibre-yaml.org/classics/crosshatch/water.png", pixelRatio: 1 }
building-hatch: { url: "https://docs.maplibre-yaml.org/classics/crosshatch/building-fallback.png", pixelRatio: 4 }
sources:
omt:
type: vector
url: "https://tiles.openfreemap.org/planet"
attribution: '<a href="https://openfreemap.org">OpenFreeMap</a> © <a href="https://www.openmaptiles.org/">OpenMapTiles</a> Data from <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
layers:
# Tangram `earth`: paper, hatched with hatch_0 repeated per tile.
- id: earth
type: background
paint:
background-pattern: earth
# Tangram `landuse`: the denser hatch_2.
- id: parks
type: fill
source: omt
source-layer: park
paint:
fill-pattern: landuse
# A data-driven pattern: open sand reads as bare ground (the light earth
# hatch); vegetation and farmland take the dense landuse hatch.
- id: landcover
type: fill
source: omt
source-layer: landcover
filter: ["match", ["get", "class"], ["grass", "wood", "farmland", "sand"], true, false]
paint:
fill-pattern: ["match", ["get", "class"], "sand", "earth", "landuse"]
# Tangram `water`: #343434 lit through a crumpled-paper normal map.
- id: water
type: fill
source: omt
source-layer: water
paint:
fill-pattern: water
- id: roads
type: line
source: omt
source-layer: transportation
filter: ["match", ["get", "class"], ["motorway", "trunk", "primary", "secondary", "tertiary", "minor", "service"], true, false]
layout:
line-join: round
line-cap: round
paint:
line-color: "#343434"
line-width:
- interpolate
- ["exponential", 1.6]
- ["zoom"]
- 13
- ["match", ["get", "class"], ["motorway", "trunk"], 2, 0.6]
- 18
- ["match", ["get", "class"], ["motorway", "trunk"], 16, ["primary", "secondary"], 10, 6]
- id: buildings
type: fill-extrusion
source: omt
source-layer: building
minzoom: 13
paint:
fill-extrusion-pattern: building-hatch
# Tangram exaggerates height by max(1, 0.5 + (1 - zoom/20) * 5) — linear
# in zoom (2.25x at z13 → 1x at z18), so one interpolate reproduces it.
fill-extrusion-height:
- interpolate
- ["linear"]
- ["zoom"]
- 13
- ["*", ["coalesce", ["get", "render_height"], 10], 2.25]
- 18
- ["coalesce", ["get", "render_height"], 10]
fill-extrusion-base: ["coalesce", ["get", "render_min_height"], 0]
fill-extrusion-vertical-gradient: true
- id: place-labels
type: symbol
source: omt
source-layer: place
filter: ["match", ["get", "class"], ["neighbourhood", "suburb", "quarter"], true, false]
layout:
text-field: ["coalesce", ["get", "name:en"], ["get", "name"]]
text-font: ["Libre Baskerville Italic"]
text-size: 22
text-max-width: 7
text-pitch-alignment: viewport
paint:
text-color: "#000000"
text-halo-color: "#f9f3e3"
text-halo-width: 3.5
  • images: + *-pattern. Every texture is a declared image. On export the textures are fetched into the document sprite and the references are rewritten to mlym:<name>. Their URLs are absolute, because emit fetches at compile time and has no page to resolve a relative path against.
  • A data-driven pattern. landcover selects its texture with ["match", ["get", "class"], "sand", "earth", "landuse"]. Both outputs are rewritten on export, with no warning.
  • light:. This is the style-spec light, anchored to the map so the shading stays put as the map turns, the way Tangram’s world-fixed lights do. Change position or intensity to retune the contrast between faces. See Light.
  • Self-hosted glyphs. The inline base style exists only to point glyphs at this site’s Libre Baskerville Italic ranges. That is an OFL font standing in for Tangram’s Baskerville italic.
  • Reproducible textures. mlym bake crosshatch --out <dir> regenerates all four PNGs from Tangram’s MIT-licensed source textures. See bake.

Map data © OpenStreetMap contributors, served by OpenFreeMap. Textures derived from Tangram (MIT, © Mapzen). Font: Libre Baskerville (OFL).