mapbox / mapbox-gl-js

Interactive, thoroughly customizable maps in the browser, powered by vector tiles and WebGL
https://docs.mapbox.com/mapbox-gl-js/
Other
11.23k stars 2.23k forks source link

Repeating symbol patterns #6295

Open natslaughter opened 6 years ago

natslaughter commented 6 years ago

background: this is based on the work here

General Idea Fill patterns that consist of repeated symbols spaced within a grid. As the map zooms in and as the space between symbols increase, new symbols fade in as the user crosses an integer zoom level.

Pattern types Currently, I have observed two types of patterns used in Mapbox maps: ‘continuous patterns’ and ‘discrete symbol patterns.’ I believe this new feature would only be for the discrete symbol patterns, but I could be wrong.

proposal-2-1

a = continuous patterns

b = discrete symbol patterns

Positioning and spacing of symbol patterns Symbols could be placed at grid intersections and center points of the grid modules. I am unsure on whether or not the user should be able to create the grid from scratch, but it's important that the user can control the 'tightness' or density of the symbol repetition. Parameters to consider:

By defining some or all of these parameters, the user should be able to produce a large variety of pattern densities. Depending on how many module shapes or grid types are available this variety would increase along with the ability to create more complex patterns.

proposal-2-2

Symbol patters with multiple symbols Maps that require a large variety of fill patterns will often combine symbols to create patterns: this allows the user to create a lot of patterns but reduce the overall amount of symbols used in the map. I could see this approach being valuable here, and one potential implementation would be to allow the user to define a second symbol used for the ‘center points’ of the grid. Personally, I would only use this option with a square grid, but the symbol combinations that emerge with hexagonal and triangular grids is interesting.

proposal-2-3

Randomness With a lot of pattern applications in older maps, patterns are hand drawn. This results in overall patterns that are not aligned to a grid, but the pattern density is evenly distributed. Some use cases could benefit from a ‘random’ grid, that doesn’t have to be strictly random, but visually looks random and symbol density is evenly distributed. One way this could be implemented is by establishing symbol points within a grid and creating a controlled range that each point can shift within.

screen shot 2018-03-08 at 3 50 22 pm

Edges Continuous patterns (a) should extend to the polygon edge, whereas patterns using symbols should remove symbols that intersect with the polygon edges. If symbol patterns are used for a polygon that is also receiving an outline, I could see a more traditional cropping of symbols to the polygon edge being attractive in some use cases.

proposal-2-4

Rotation & Pitch Regarding rotation, I could see both options attractive to have: having the symbols rotate with the map, and being fixed (not rotating). Regarding pitch, symbol anchor points should be translated to the map, not the viewport. However, when the map is pitched, symbols might look best if ones in the foreground were larger than symbols in the background.

Label collision Having an option to create a subtle patterns that labels sit on, and also more prominent patterns that avoid label collisions would be ideal.

cc @ansis @kkaefer @eschow @nickidlugash

stevage commented 6 years ago

See also https://github.com/mapbox/mapbox-gl-js/issues/1831

anandthakker commented 6 years ago

@natslaughter 😍 great breakdown of this idea/proposal!

Brainstorming (very, very roughly) how all the facets laid out above could be captured in a spec definition. It seems like a Pattern could consist of:

{
  cellShape: 'square' | 'triangular' | 'hexagonal'
  cellSize: number // size in pixels of one "cell" (aka grid module)
  overflow: 'clip' | 'hide' // determines which edge behavior to use

  images: [ // any number of images (symbols) can be placed in a cell
    {
      image: string // name of sprite
      width: number // in pixels
      height: number // in pixels
      position: [x, y] // offset from center of the cell in pixels, defaults to [0, 0]
      // if the image would overflow past the cell's boundaries, either clip it, or else "wrap"
      // it around to the matching edge of the tile -- the latter would only be possible with
      // something like the existing scaling behavior
      overflow: 'clip' | 'wrap' 
      collisionGroups: array<string> // controls which, if any, "collision groups" this image would interact with
    },
    ...
  ]
}

True randomness would be problematic for the reasons mentioned in https://github.com/mapbox/mapbox-gl-js/issues/5853#issuecomment-351452558, but something like Perlin Noise might be able to achieve similar results while still producing deterministic results. So, we could provide some kind of "noise" expression that could be used for an image's position: to produce a "random" effect (in either the x, the y, or both).

anandthakker commented 6 years ago

^ Updated the above to avoid using the word "tile" in the context of tessellations 😬 , and also to add collisionGroups to an image (see https://github.com/mapbox/mapbox-gl-js/pull/6028), though it might be simpler to have collision behavior be a top-level property of a pattern rather than per-image.

nickidlugash commented 6 years ago

@natslaughter @anandthakker awesome descriptions!

I believe this new feature would only be for the discrete symbol patterns, but I could be wrong.

Based on the above explanation of the two, I think that continuous symbol patterns could technically be subsets of discrete symbol patterns, where cell shape and cell size match the image shape and size, and the edge behavior is to crop (overflow: 'clip'). Our current fill pattern implementation creates patterns with square images on a square grid, but it would be cool to be able to create continuous hexagonal or triangular patterns as well. It would also be great to be able to control the rotation and pitch behavior of continuous patterns.

I'm assuming that the suggested scaling behavior for this proposal is the same as in the referenced background ticket: to scale the space around the image and fade in another image when there is enough room. I think this scaling behavior is really the only difference between continuous and discrete patterns. Would it make sense to try to combine both pattern types into a single fill pattern style layer type, and have the scaling behavior be a property (scale the space/cell, or scale the symbol)?

Symbols could be placed at grid intersections and center points of the grid modules.

@natslaughter can you elaborate further on this choice of placement system (versus the more typical approach of placing one symbol at the center point)? Is it required to enable the consistent placement of existing symbols across zoom levels? If so, that seems reasonable, but I think that it does mean that we lose most of the visual distinction between the different cell shapes (assuming there is only one symbol per pattern). I think with this approach, the hexagon and triangle options are technically the same.

Symbol patterns with multiple symbols

What additional flexibility does this give users over creating a single "symbol" that has multiple icons? Do you think it's enough additional flexibility to justify adding this option?

stevage commented 6 years ago

My 👏 for the OP write up as well :) Just wanted to chip in a couple of concrete examples in case that's helpful.

screen shot 2018-03-09 at 3 08 03 pm

The water texture in this one maybe falls a bit between the cracks of "continuous" and "discrete" patterns. Ideally, I'd have each curve placed randomly. Trying to hide the seams at the edges of the tiles was quite tricky, and not entirely successful. (see it at hipstermelbourne.org)

screen shot 2018-03-09 at 3 11 00 pm

This grass texture is an instance where quasi-randomly placed symbols could work really well. And if "symbol patterns with multiple symbols" is supported, it would be great here. (You can see my pattern is trying to break the monotony of using the same symbol everywhere.)

screen shot 2018-03-09 at 3 16 51 pm
natslaughter commented 6 years ago

can you elaborate further on this choice of placement system (versus the more typical approach of placing one symbol at the center point)?

@nickidlugash thanks for asking this question. In answering it I realized that just using center points to position symbols is the way to go. 👍 In using center points and intersections, I was visually trying to space symbols in a way that looked good, although by scaling the underlying grids, I can get the same symbol spacing. That said, I was not able to reproduce the above "square grid" spacing without a: positioning a symbol every other center point (a.2), or b: rotating the grid 90º - creating a diamond grid (b.3). I think it would be worth having an extra grid cell shape (diamond), since this symbol spacing is quite nice. Or, if we should only have one, four sided cell, I would propose having the diamond grid versus the square grid. Also, I am unsure on how useful the triangular grid would be for patterns, which I changed to a perfect triangle in the chart below (d.3).

screen shot 2018-03-09 at 12 00 16 pm

cc @ansis @anandthakker

anandthakker commented 6 years ago

@natslaughter @nickidlugash I think it might still be useful to allow multiple symbols/images to be placed in a cell -- this would mean, for example, that you could have several different patterns that are composed of a few common symbol building-blocks.

Regarding positioning of symbols within a cell: I suggested a position property above, which would (1) be necessary for having multiple images per cell, and (2) be useful anyway if you wanted to offset your image from the cell's center.

Here's a super quick Observable notebook for exploring this: https://beta.observablehq.com/@anandthakker/patterns

anandthakker commented 6 years ago

I think it might still be useful to allow multiple symbols/images to be placed in a cell -- this would mean, for example, that you could have several different patterns that are composed of a few common symbol building-blocks.

@jfirebaugh pointed out in chat that this could be achieved by "vector icons". Since that's the case, I'm with you @natslaughter @nickidlugash on just sticking with positioning the icon at the center of a pattern cell

natslaughter commented 6 years ago

@ansis & @anandthakker I could see having a more radial grid being very useful as well. I'm unsure on how to build this, as the way I have mocked it up is a bit different from the previous work documented on this ticket, but here is a diagram showing the idea. With a lot of stippling in engraving and lithography, these stippling wheels were used that created fields of radial dots. This created a pattern with uniform density but the repeating pattern cell wasn't recognizable at all.

screen shot 2018-03-13 at 9 46 13 am

zerda-ocm commented 4 years ago

I'd like to add a similar approach, but with more random patterns: It's called "Recursive Wang Tiles for Real-Time Blue Noise".

See https://johanneskopf.de/publications/blue_noise/ for details or this video https://www.youtube.com/watch?v=ykACzjtR6rc for a good explanation.

I really want such a feature for fill patterns (ideally in such a way, that symbols avoid the edges of the polygon so they won't be cut off). I might give it a try to implement it myself, but I fear my programming skills are way too limited for that.