Tous les articles par Mathieu Rajerison

Line meteor gradients with data-defined start and end colors

Doing line gradients in QGIS is an interesting challenge.

Anita Graser gave a hack in a post.

Lutra Consulting had an idea of gradient colored meshes, but they didn’t reach their goal to develop it.

But the question asked by Tammo on Twitter was quite special. Tammo wanted to apply gradients on lines, and data-define the starting and ending color along a certain color ramp.

Here is my result, that I also gave on StackOverflow. It uses a hack based on interpolating points along a line and coloring them based on their position.

How to do this ?

First, set your line to geometry generator to Points

Then, apply this expression to have interpolated points along the line :

collect_geometries(
  array_foreach(
    generate_series(0, length($geometry), 0.000001), -- here, we add a point every 0.000001 unit (I am in WGS84)
    line_interpolate_point($geometry, 
    @element
  )
)
You can not see it, but there are very little points !

Here is a zoomed-in image of the line :

The points are very near from one another. I don’t know if you can see them. That’s why it looks like lines.

Finally, just set the filling color according to the position of the point along the line, and along the subsetted color ramp :

ramp_color( -- this function allows you to select one particular color along a color ramp
  'Viridis', -- I ❤ Viridis
  scale_linear(
    @geometry_part_num, -- @geometry_part_num is the number of the point along the line (its position)
    0, @geometry_part_count, -- @geometry_part_count is the number for points along the line
    "start"/10, "end"/10 -- my values are between 0 and 10, so 10/10 gives 1 (the max of the color ramp)
  )
)
Here it is !

What if you made them meteors ?

Just set the size of each point from a min to a max :

scale_linear(
  @geometry_part_num,
  0, @geometry_part_count,
  0, 10 -- 10 : max size
)

So, what could be the use of such a rendering ?

Imagine you want to visualize the routes that connect the places with the most people, or that will connect places with few people.

Or, for instance, imagine you want to see the “cost” of going from one place to another, depending on relief.

This type of rendering could be useful to see the change of a particular variable between two states (before – after or origin – destination).

The power of array in qgis : match two sets of lines together

On Georezo, a French forum, someone asked how to retrieve the streets closest to pipelines.

The QGIS expression

So, let’s suppose we have one data for pipelines (source) and one for streets (target), here is a QGIS expression that retrieves the street data object index closest to each pipeline.

with_variable(
  'mean_distances',
  aggregate(
    'streets', 
    'array_agg', 
    array_avg(
      with_variable(
        'nodes',
        nodes_to_points(geometry(@parent)),
        array_foreach(
          generate_series(1, num_points(@nodes)),
          distance(point_n(@nodes, @element), $geometry)
        )
      )
    )
  ),
  array_find(@mean_distances, array_min(@mean_distances)) + 1
)

Github repo

Here, you’ll find a complete reproducible project with layers and expressions

Tutorial

One way to master QGIS field calculator expressions is to conceive them progressively, iteratively. Let’s decompose it.

Add a virtual field named nearest_with_nodes as integer which will store the index of the perfect target candidate, that is the index of the closest street line.

Open the Field Calculator window and follow this tutorial.

One street can be retrieved with the expression

get_feature_by_id('streets', 1)

Here it is the first object

To get the distance to this street from the current object, we calculate

distance(get_feature_by_id('streets', 1), $geometry)

But this distance is not ideal, because it is the closest space from one line to the other. If nodes are spread out, this does not reflect the “true” distance.

The “true” distance can be approached by calculating the distance from each of target object nodes to the current source line.

To get the nodes from the target line, we use

nodes_to_points(geometry(@parent) -- @nodes variable

To get the first node of the target line, we use

point_n(@nodes, 1)

To get the distance to this node

distance(point_n(@nodes, 1), $geometry)

But we have to compute it to each node.

We get the number of point with num_points(@nodes)

So we use generate_series to create the indexes of the nodes, going from 1 to the number of nodes. If 3 nodes, we’ll have [1,2,3]

generate_series(1, num_points(@nodes))

And we use array_foreach to calculate the distance to each node

array_foreach(
generate_series(1, num_points(@nodes)),
distance(point_n(@nodes, @element), $geometry)
)

Now we have an array of distances, let’s say [1, 3, 2], we want to get the average of the distance. For this, we use array_avg available after installing the ArrayPlus plugin.

array_avg(
  with_variable(
    'nodes',
    nodes_to_points(geometry(@parent)),
    array_foreach(
      generate_series(1, num_points(@nodes)),
      distance(point_n(@nodes, @element), $geometry)
    )
  )
)

And we calculate this mean distance from source line (pipeline) to each of the target lines (streets).

For this, we use aggregate function

aggregate(
  'streets',
  'array_agg',
  array_avg(
    with_variable(
      'nodes',
      nodes_to_points(geometry(@parent)),
      array_foreach(
        generate_series(1, num_points(@nodes)),
        distance(point_n(@nodes, @element), $geometry)
      )
    )
  )
)

Now, we have an array of mean distances, one mean distance per target line.

But we want to retrieve the min mean distance, and its index. Its index will be the one of the closest line.

To get the min distance, we use

array_min(@mean_distances)

We find the index of the min mean distance, in the array

array_find(@mean_distances, array_min(@mean_distances))

We add one because the first element of the array is indexed 0

array_find(@mean_distances, array_min(@mean_distances)) + 1

Here is the complete expression :

with_variable(
  'mean_distances',
  aggregate(
    'streets',
    'array_agg',
    array_avg(
      with_variable(
        'nodes',
        nodes_to_points(geometry(@parent)),
        array_foreach(
          generate_series(1, num_points(@nodes)),
          distance(point_n(@nodes, @element), $geometry)
        )
      )
    )
  ),
array_find(@mean_distances, array_min(@mean_distances)) + 1
)

So I hope you understood how to find the closest line to another one and the logic behind creating sophisticated expressions.

Display the street name

Let’s say the street layer has “name” attribute.

You can display as a label informations for the target line like this:

attribute(get_feature_by_id('streets', nearest_with_nodes), 'name')

Conclusion

Interestingly, you can see you can execute sophisticated tasks internally with expressions in the field calculator without making any SQLish virtual layers or applying magic python code.

That makes the Field Calculator a powerful and fun-to-use tool

To me, ArrayPlus should benefit from higher visibility as adding really interesting operators in QGIS. A user had to remain me of its existence. It would be nice if it joined the panoply of Field Calculator functions.

Bonus

What if you put many points on each target line ?

We can do this with interpolate_line_point

Here is the expression :

with_variable(
	'mult',
	10, 
	with_variable(
		'mean_distances',
		aggregate(
			'streets',
			'array_agg',
			with_variable(
				'feat',
				geometry(@parent),
				with_variable(
					'distances',
					array_foreach(
					generate_series(0, length(@feat), length(@feat)/((num_points(@feat)-1) * @mult)),
					distance($geometry, line_interpolate_point(@feat, @element))),
					array_avg(@distances)
				)
			)
		),
	array_find(@mean_distances, array_min(@mean_distances)) + 1
	)
)

geosparklines, a twitter map story

On the 10th of April, I created a map to see the evolution of COVID cases in France as spatialized sparklines.

I first saw it like a good use case on how to use some QGIS related functions (QGIS is an opensource geospatial tool)

“Les Artisans cartographes” mentioned the map on twitter. Thereafter, many people started retweeting it. I didn’t expect it to make so much “noise”

For me, this representation was an efficient way to see evolutions on a map because you could observe the dynamics at a single glance.

Maybe this instantaneous look can help in emergency cases because you don’t need to press play and wait for every frame to complete in order to get “the big picture”.

For me, there was nothing much innovative or disruptive about this map. It had been a long time the idea was in my head and I had already experimented it around census data.

I thought someone had already created something similar. But Alberto Cairo, a world-reknown dataviz expert, seemed to have never seen something like this before, which surprised me.

Kenneth Field, who is a reknown map maker compares this kind of map with Minard’s (a great honor !)

Read this wonderful article around Minard

Well before I put the QGIS code to create the map on github and explained it on this blog, some people on twitter started to create their own. Later, I saw some similar maps emerge.

That’s a gallery of these initiatives you’ll discover here.

Italy

Czech Republic

Also this one :

Holland

US

Wisconsin

Later, Kemper Smith made a US version

Washington Post

You can see that the Washington Post map is more sophisticated, with additional use of thickness to illustrate the current state (actual number of cases) and color to support the evolution

Note the Washington Post map was showcased on flowingdata, which is a reference media relaying dataviz experiments (also tutorials)

My experiments around this idea

An animated, worldwide and colored version

A hairy version

A scribbled version