Tiled & woven thematic maps

David O’Sullivan (with Luke R Bergmann)

A preview

The TL;DR;

Some other multivariate mapping approaches

This is a perennially challenging problem, so many different ways of tackling it have been attempted…

A little more context for these was provided in our earlier talk.

Our approach

Two new kinds of thematic map for handling complex multivariate data associated with polygons.

We’ve developed python code that helps you make tiled and woven maps.

Tiling theory

Mosaic world map by Chris Chamberlain; see this article and this video for more.

A plane tiling is…

“… a countable family of closed sets \(\mathcal{T}=\{T_1,T_2,\ldots\}\) which covers the plane without gaps or overlaps. More explicitly, the union of the sets \(T_1,T_2,\ldots\) (which are known as the tiles of \(\mathcal{T}\)) is to be the whole plane, and the interiors of the sets \(T_i\) are to be pairwise disjoint” (Grünbaum and Shephard 1987, page 16)

… a GIS coverage!

How many tilings?

It’s a lot.

Systematic enumeration up to a certain complexity yields a ‘galaxy’ of 2.4 billion tilings (Zeller et al. 2021). You can explore the galaxy here: Tegula.

Grünbaum and Shephard repeatedly narrow focus to tilings with specific properties so they can say anything at all.

Symmetry in mathematical tiling theory

The symmetries of a tiling map tiles on to other tiles.

Sets of tiles mapped onto one another like this are transitivity groups, of which an isohedral tiling has only one.

Cartographically it probably doesn’t work like that…

Symmetry in cartographic tiling theory

Directionality or orientation is irrelevant to mathematicians.

However, it is important for mapping: we need a concept of cartographic transitivity groups where different tile orientations ‘count’.

What we learned from tiling theory

¯\_(ツ)_/¯

We are not entirely sure… but we’ll keep looking!

Tile units

The building blocks of our approach.

The basic pattern

cairo_tile = TileUnit(tiling_type = "cairo").transform_rotate(-45)
cairo_tile.plot(r = 2, show_vectors = True,
                figsize = (7, 7)).set_axis_off()

Triangles, squares, hexagons

Archimedean tilings by regular polygons

Laves (the Archimedean duals)

Dissections and colourings

In general

These (and more) are made the same way:

tile = TileUnit(
  tiling_type = """cairo|archimedean|laves|hex-dissection|
  square-colouring|hex-colouring""",
  n = ..., code = "3.3.4.3.4", dissection_offset = 0|1,  # optional
  spacing = 500, crs = 2193
)

We can also make various adjustments:

tile = tile.transform_rotate(...)
tile = tile.transform_scale(...)
tile = tile.transform_skew(...)
tile = tile.inset_tile(...)
tile = tile.inset_elements(...)
tile = tile.scale_elements(...)

‘Insetting’

Insetting relative to the repeating tile unit helps distinguish elements.

Weaving

For a lot more on woven maps, see our earlier talk.

Weaving theory ≫ tiling theory!

Weaving theory leads to useful matrix-based approaches.

Primarily for biaxial weaves.

We have extended the matrix approach to triaxial weaves (see our notes).

Weave units

We make these the same way we make tile units.

w1 = WeaveUnit(weave_type = "twill", n = 3, aspect = .9, 
               strands = "ab-|cde-")

w2 = WeaveUnit(weave_type = "cube", aspect = .8, 
               strands = "a-c|d-f|g-i")

The strands parameter specifies which strands in each axis (separated by the |) are distinct, and also allows us to ‘skip’ strands on - characters .

Making a map

Vermeer’s The Geographer (∽1675) from commons.wikimedia.org.

Tiling a dataset

TileUnits and WeaveUnits are both Tileable objects that can be used to make a Tiling.

To make a Tiling we also need a polygon dataset to be tiled

tiling = Tiling(cairo_tile, region)

The tiling process

Getting a tiled map

Next, get a TiledMap from the the Tiling.

tiled_map = tiling.get_tiled_map(prioritise_tiles = True)

This overlays the Tiling with the geospatial data polygons, pulling their data, and dissolves based on tile element IDs.

We emphasize tile boundaries or zone boundaries in the data depending on the prioritise_tiles setting.

At this point, you can export to a dataset.

tiled_map.to_file("my_tiled_map.gpkg") 

Designing the final map

We have to specify the mapping from tileable element identifiers ("a", "b", "c" …) to data variables.

tiled_map.variables = dict(a = "Rank_Emplo", b = "Rank_Incom", 
                           c = "Rank_Crime", d = "Rank_Housi")

We also specify a mapping from variable names to colour palettes.

tiled_map.colourmaps = dict(zip(tiled_map.variables.values(),
                                ("Reds", "Blues", "Greens", "Greys")))

Now we can render a map…

fig = tiled_map.render(use_ellipse = True, figsize = (12, 5.5),
                       legend_dx = -0.05, legend_dy = 0.05)

There are a range of options here particularly in relation to the tricky business of generating a legend.

Some more examples

Because we can…

Conclusions

Image from publicdomainpictures.net by Andrea Stöckel

Thoughts

This work is an exploration of the combined effect of pattern, orientation, texture, and colour as visual ‘multi-variables’. Along the way we’ve seen some things that appear promising:

  • Contrasting variables by applying the same colour scheme to multiple attributes
  • Emergent multivariate regions (with uncertain boundaries)
  • (Related) an overall sense of complex landscapes

Challenges

There is plenty to think about:

  • Using colour well is hard at the best of times—using several colours at once is even harder!
  • Potential for use in combination with hierarchical spatial indices like H3?
  • Legends are a challenge—ideas welcome!
  • We’ve worked hard to make the code easy to use…
  • … but it’s still code

Further work

The code used to make this talk is available at github.com/DOSull/weaving-space.

We are keen to get feedback, contributions, and ideas, especially:

  • Actual users of the approach
  • QGIS plugin development

If you have a project where this approach might work, please reach out!

Questions?

github.com/DOSull/weaving-space