Plotting¶
ls.plot draws anything Landschaft reads or makes, as the data call for: a
raster or a vector layer on an interactive map over satellite imagery, a table
as a chart, the result of a tool as its maps, charts and numbers together. One
call, whatever the input:
import landschaft as ls
forest = ls.load_raster(ls.example_data("input"))
p = ls.plot(ls.mspa(forest))
p.save("mspa.html").name # 'mspa.html'
In a notebook (Jupyter, JupyterLab, VS Code, Colab) the plot shows inline. In a script it opens in a window of its own (pywebview, installed with Landschaft), or in the web browser where no window can open.
A plot saved as .html is a single file that opens without a network connection:
the map library and the chart library are in the page, only the satellite imagery
and the other base maps come from the web. Send it to a colleague or open it in
the field; with basemap=None nothing at all is downloaded.
What is drawn, and how¶
| Input | Plot |
|---|---|
| a product of a tool (MSPA, FAD, FOS, landscape mosaic, accounting, distances...) | its classes in GuidosToolbox's colours, with their names in the legend, in GuidosToolbox's order |
| a raster of integers with at most 64 values, or booleans | classes, each in a distinct colour |
a floating-point raster of at most 12 whole numbers (a class map read with masked=True, NaN for its NoData) |
classes, each in a distinct colour |
| any other raster | a colour ramp from the 2nd to the 98th percentile |
a stack (band, y, x) |
one layer, on one scale for all its bands, the band shown chosen on its legend card (a slider, and ▶ to play through them); red, green and blue bands as true colour |
| a GeoDataFrame, GeoSeries or geometry | polygons, lines or points, coloured by column= |
| a DataFrame or Series | bars, lines or one chart per metric |
| a tool's result (Zonation, habitat graphs, Biodiverse, PCA, clustergrams, least-cost paths, results with several rasters) | its maps, charts and summary numbers |
a species distribution model (ls.sdm.fit) |
its response curves and cross-validated scores; with env= (the predictors), the suitability map of each model and of the ensemble, with the presences |
a change between two dates (simple_change, morphological_change, fos_change, fad_multiscale_change) |
the change map with its classes named, the flows between the classes as a Sankey diagram, the counts |
| a list of rasters and vectors | one map, one layer each |
The ramp follows the name and the values of a raster: priority ranks (rank,
priority) on Zonation's rank colours, currents (current, cum_currmap,
flow_potential) on a log scale, elevations (dem, elevation) on a terrain
ramp, NDVI from red to green, values on both sides of zero on a diverging ramp
centred on zero. NoData is transparent, and so is the Missing class of a
product (the area outside the map): the imagery shows through.
Reading the map¶
Move the cursor over a map and the box at its bottom left gives the coordinates
and, for every raster drawn, the value under the cursor: the name of a class
(Core, Bridge in Edge...) or, on a colour ramp, the value to 1/254 of the
ramp's range (≈ 1006; ≤ / ≥ beyond its ends). Click to keep them in a
popup. The values are read from the image in the page, so they are those of the
map drawn: block means where a large raster was reduced to the screen.
Detail as you zoom in¶
While Python runs, a map draws the part in view in full detail as you zoom in:
past the resolution of the overview the page holds, it asks Python for the
cells in view at the screen's resolution, in the colours of the overview (a
raster of 50 000 × 50 000 cells shows its own cells, not block means), and the
value under the cursor is read from them. Vector layers drawn as an image are
burnt again for the view; WebGL layers get the features in view simplified to
the screen. This works in a notebook (Jupyter, JupyterLab, VS Code: a widget of
no size, under the map, carries the questions to the kernel) and in the window
a script opens (pywebview), with no server and no port. A page saved, or a
notebook opened again without its kernel, keeps the overview. The environment
variable LANDSCHAFT_PLOT_LIVE=0 turns it off.
A stack is a time series: once the view rests, the other bands are drawn for it ahead, the nearest first (the 41 bands of a 5000 × 5000 series in about 0.4 s), so the band slider and the play button show every band sharp at once. The details of the bands seen are kept (decoded up to 256 MB, the rest as PNG). A detail takes 15 to 40 ms: the bands of a stack share the reprojection of their grid, worked out once per view in C++ and exact to the pixel.
Every layer has a card in the legend, with a slider for its opacity (to see the imagery underneath); a card hides while its layer is switched off in the layer control (top right). The button under the zoom buttons shows the map full screen.
Comparing two dates¶
compare=True draws two rasters (or vector layers) on one map, the first on the
left of a line, the second on its right; drag the handle to sweep between them:
forest = ls.load_raster(ls.example_data("input"))
after = forest.where(forest.x < forest.x[400], 1) # the east cut
p = ls.plot([ls.mspa(forest), ls.mspa(after)], compare=True, title="Before / after")
Two maps of the same colours share one legend card. The values under the cursor are read on both sides.
Choosing the colours¶
The defaults can all be changed in the call:
dem = ls.load_raster(ls.example_data("orelevation"))
p = ls.plot(dem, cmap="magma", vmin=0, vmax=1500, title="Elevation")
landcover = ls.load_raster(ls.example_data("clc"))[:400, :400]
p = ls.plot(landcover == 23, cmap={0: "#f4f1de", 1: "#2d6a4f"},
labels={0: "other", 1: "broad-leaved forest"})
cmap: a palette name ("viridis","magma","terrain","rdylgn","rdbu","blues","greens","ylorrd","rank","current", or any matplotlib colormap;"_r"reverses it), a list of colours, or a{value: colour}dictionary for classes;labels:{value: name}, the names of the classes; a class given a transparent colour ("#00000000") is left out of the map and the legend;categorical:Truedraws the values as classes,Falseon a ramp;vmin,vmax,log: the range of the ramp and a log scale, for rasters and for the column of a vector layer; values beyond the range take the colour of its end, marked≤and≥in the legend;bands: the bands of a stack to draw, by their names or the band numbers of the file (bands=[4, 3, 2]);column: the column of a vector layer that colours it;basemap:"satellite"(default),"osm","topo","light","relief"orNone; the others stay selectable in the map's layer control.
Static figures¶
static=True draws a matplotlib figure instead, for papers and reports; the
satellite imagery under it comes from contextily.
An interactive plot saved as .png, .pdf or .svg is drawn the same way.
A map has a scale bar (a round length, measured on the ground: Web Mercator and
degrees are corrected for latitude) and a north arrow; a single map on Web
Mercator or degrees has longitudes and latitudes on its axes. Several maps come
as small multiples: the bands of a stack (on one colour scale, one colour bar),
the rasters of a result, or two dates with compare=True. Layers of the same
colours share one legend. Vector layers take the colours of the interactive map
(column=, cmap=, labels=...). The figure's options are keywords:
figsize=(w, h) in inches, ncols=, scalebar=False, north_arrow=False.
p = ls.plot(forest, static=True, basemap=None)
p.figure # the matplotlib figure
p.save("forest.png").name # 'forest.png'
stack = ls.load_raster(ls.example_data("orelevation")).expand_dims(band=["a", "b"])
p = ls.plot(stack, static=True, basemap=None, ncols=2, figsize=(9, 4))
Speed¶
A raster is reduced to the resolution of the screen (max_size=2048 cells on
the long side) before anything else: block means in C++ for continuous values,
one cell per block for classes, and a raster read lazily (chunks="auto") is
read reduced, in strips, or through its overviews when it has some. It is
reprojected to Web Mercator once and written as an 8-bit PNG with a palette.
Vector layers of up to 20 000 features go to the map as GeoJSON, simplified to
half a pixel. Larger ones, up to 2 million features, are drawn by the graphics
card (WebGL): simplified to an eighth of a pixel, cut into triangles in C++ and
packed into the page, they stay sharp when zoomed in, and the value of the
feature under the cursor shows as for rasters, its columns in a popup on a
click (200 000 polygons in about a second). Even larger layers are drawn as an
image. WebGL needs a browser of the last few years (any recent Chrome, Edge,
Firefox, Safari or VS Code). A static figure draws all the
features of a layer at once (50 000 polygons in under a second) and burns more
than 100 000 into an image. The bands of a stack share 16 million pixels, so a
page of many bands stays light. A map of 100 million cells takes a fraction of
a second; see Benchmarks.
In tests and in scripts that only save plots, the environment variable
LANDSCHAFT_PLOT_SHOW=0 keeps windows from opening.