Janus / Guides / Maps to numbers

Analysis · class maps, polygons, areas, zones

From class maps to areas, polygons and tables.

A land cover map answers questions with numbers: how many hectares of forest each year, which patches were cleared, how much of a property or a park changed. This guide takes 34 years of MapBiomas over the coast of Sergipe, puts them on the grid of a Landsat series, counts the area of each class every year, turns the forest of 1985 into polygons with their hectares, and follows each patch through every year that came after.

01The idea

A class map is a grid of codes; a report wants hectares, polygons and tables. Five tools of Janus go between the two, each a task in Tools → Tasks whose result opens as a layer and keeps its provenance (what was run, on what, with which options):

Reproject, resample, clip
Tools → Data preparation → Reproject, resample, clip...: a series' files through gdalwarp's own code, to a CRS, a pixel size or the grid of another layer, so two maps line up pixel for pixel.
Areas
Analysis → Areas: the area of each class at every date, each pixel counted with its true area on the ellipsoid, in ha, km² or %.
Polygonize
In a map's or a series' menu in the Layers panel: the patches of a map as polygons in a GeoPackage, each with its class, its area and its perimeter.
Rasterize
In a vector layer's menu: its features burned on the active series' grid, as a mask, their numbers or an attribute; a Zeit tool can then run only inside them.
Zonal statistics over time
In a vector layer's menu: each feature a zone, through every date of a series: the statistics of its values, or the area of each class, as CSV tables and a GeoPackage.

02The data

Two layers of the same coast, around Aracaju, the capital of Sergipe, in north-eastern Brazil:

landsat
The 41 annual Landsat medoid composites of a vegetation index of the trend test guide (path/row 215/067, 1985 to 2025, EPSG:4326 at about 30 m), cut to a window of 2000 × 2000 pixels, about 59 km a side, with 0 declared as no data (one VRT a year, made with GDAL).
mapbiomas
MapBiomas collection 4.1, Atlantic Forest biome, one map a year from 1985 to 2018. Each year is a GeoTIFF of the whole biome (77 947 × 94 203 pixels); here a box 200 pixels wider than the window on every side was cut from each (a VRT a year), with 0 as no data and the collection's class names and colours added to the band.

MapBiomas codes its classes by number: 3 is Forest Formation, 5 Mangrove, 15 Pasture, 20 Semi-Perennial Crop (sugar cane), 21 Mosaic of Agriculture and Pasture, 24 Urban Infrastructure, 33 River, Lake and Ocean. Janus reads the names and colours from the band and shows them in the Classes panel; a map without them can be named there by hand.

03Onto one grid

Two maps can be compared, combined or used one as the other's mask only when their pixels coincide. Tools → Data preparation → Reproject, resample, clip... warps the active series' files (or a run's map) and opens the result as a new series:

The Reproject, resample, clip window: Layer mapbiomas (34 files); To: A CRS, EPSG:4326 typed below; Pixel size unticked, as gdalwarp chooses; Resampling mode; Area Whole layer; No data unticked, the source's; Type as it is; Format COG (GeoTIFF and Virtual the other choices); Name mapbiomas warped; under it the arguments gdalwarp -t_srs EPSG:4326 -r mode -of COG -co COMPRESS=DEFLATE -co PREDICTOR=YES -co BIGTIFF=IF_SAFER -co NUM_THREADS=2 -multi -wo NUM_THREADS=2 -overwrite, and a Run button.
The window as it opens on the MapBiomas years: to a CRS (EPSG:4326 at first), mode for a map of classes, the whole layer, COGs, and the gdalwarp arguments those choices make.
To
A CRS (EPSG:31983, ESRI:102033, a PROJ string, a WKT), the CRS of a layer, or the grid of a layer: its CRS, origin, pixel size and extent, so the maps align pixel for pixel. Otherwise Pixel size sets the cells, optionally aligned on multiples of the size (-tap).
Resampling
near, bilinear, cubic, average, mode, min, max or med. Mode, the most frequent class under each new pixel, is the default for a map of classes; near otherwise.
Area
The whole layer, the visible area, the ROI, or the polygons of a vector layer: the pixels outside them without a value, the result cropped to them (-cutline).
Format
Cloud-optimized GeoTIFFs, tiled GeoTIFFs, or Virtual: warped VRTs, nothing written, ready at once, the pixels computed as they are read.

The window writes the arguments of the gdalwarp command it runs, and the task's log keeps them: the result is the command's, bit for bit. Here, with To set to The grid of a layer, landsat, the arguments become -t_srs the layer's CRS, -te its extent and -ts 2000 2000, with mode. Run as the gdalwarp command on 2 threads, the 34 years took 10.5 s. MapBiomas and these Landsat composites share a pixel size (0.000269494585236°) and their origins fall on it, so the warp cut the maps without moving a class: every one of the 34 × 4 million pixels equals the source's.

Janus with two layers, mapbiomas_grid (34 dates, 1985 to 2018, 2000 x 2000 px, Byte, WGS 84) over landsat (41 dates). The map at 2018, zoomed in: pasture in yellow, mosaic of agriculture and pasture in cream, sugar cane in mauve, annual crops in pink, forest in dark green, urban in red, a river and mangroves on the right. Pin 1 in a pasture at the top, pin 2 in a large forest at the bottom. The chart, steps and markers: pin 1 at forest from 1985 to 1989 and pasture from 1990, pin 2 at forest every year. The Statistics table for pin 1: class at the date Pasture (15), majority class Pasture 85%, 2 classes seen, 1 change, last change 1990: Forest Formation (3) to Pasture (15).
On the Landsat grid: MapBiomas in 2018, a pin in a forest cleared for pasture in 1990 (1) and one in a forest that stayed (2).

The class names after a warp

The gdalwarp command carries a map's colour table to the new files but not its class names; Janus's warp copies the names too. The years here were warped with the command, so their names were written back beside the files (a .aux.xml each, with GDAL), as naming them once in the Classes panel would.

04Areas by class and date

Analysis → Areas (or Areas... in the Classes panel) counts the classes of the active series at every date, at full resolution, on the whole layer or the ROI: Count starts a task. A pixel's area is not the same everywhere:

Equal-area projection
The geotransform's pixel area, the same everywhere.
Longitudes and latitudes
Each pixel's geodesic area on the CRS's ellipsoid: a degree of longitude shrinks towards the poles. Here, from 879.29 m² on the window's northern edge to 877.75 m² on its southern one, 878.53 at the centre.
UTM and other projections
Each pixel's geodesic area too, the note saying how far the nominal area is from it: in UTM the projection's scale, 0.05% off 110 to 230 km from the central meridian.

The geodesic areas are measured (with geographiclib) on a grid of at most 129 × 129 points across the map and interpolated between them, so the count costs no more than a histogram: the 34 years of 4 million pixels took 0.9 s on 2 threads.

The Analysis panel at its Areas tab, the map in a window of its own: Map mapbiomas_grid (each date), whole layer, Count; units ha (ticked), km², %; Copy CSV, Save CSV; the note: pixel areas, longitudes and latitudes, each pixel's geodesic area, 877.75 to 879.29 m² (878.53 at the centre), 0.9 s, the CSV's folder. A bar per year from 1985 to 2018 with the classes stacked, about 260 000 ha each: forest in dark green at the bottom, pasture in yellow, the mosaic in cream, the legend of the 17 classes over the last years' bars. Below, the table of 2018: Forest Formation 397 149 pixels, 34 894.19 ha, 13.46%; Mangrove 9 266.42 ha, 3.57%; Pasture 1 174 447 pixels, 103 203.16 ha, 39.80%; Semi-Perennial Crop 19 827.84 ha, 7.65%; Mosaic of Agriculture and Pasture 63 270.88 ha, 24.40%; Urban Infrastructure 13 105.80 ha, 5.05%.
34 years in hectares: a bar per year with the classes stacked, and the table of the year chosen (2018) in pixels, ha, km² and %.

The bars are in ha, km² or %; Copy CSV and Save CSV... give the whole table, and areas.csv is written in the task's folder (date, value, class, pixels, area_m2, area_ha, area_km2, percent, a row per class and year). The mapped area is 259 297 ha every year. Some of its rows:

Class1985 (ha)2000 (ha)2018 (ha)
Pasture122 807146 248103 203
Mosaic of Agriculture and Pasture69 23646 77863 271
Forest Formation36 89537 13234 894
Semi-Perennial Crop1 8171 82719 828
Urban Infrastructure4 0866 44213 106
Mangrove8 3959 4229 266

Sugar cane grew elevenfold, almost all of it after 2000; the city tripled. The forest's total hardly moved: 36 895 ha in 1985, 34 894 in 2018, between 33 205 (2006) and 38 280 ha (2012) in between. Whether the same forest stood all along is another question, which the polygons answer.

Mapped area, not adjusted area

These are areas as mapped: pixels times their area, the map's errors included. The Validation guide estimates a class's area with its confidence interval from a sample. For a map of change years (LandTrendr's year of loss, CCDC's break), Area by value... in its menu counts the area by year.

05Polygons

Polygonize..., in the menu of a series (its date shown) or of a run's map in the Layers panel, turns the map's patches, the pixels of one value joined by their sides or corners, into polygons in a GeoPackage that opens as a vector layer. Each polygon gets:

value, class
The map's value and the class's name.
date
For a map of change dates in decimal years: the date (2019-06-14), or the year when it is whole.
area_ha, perimeter_m
Geodesic, on the CRS's ellipsoid, whatever the projection.
pixels
Its pixels.
mean_<map>
With Mean of: the mean of another map of the same grid in the polygon (a loss's magnitude).

A Minimum area first merges each smaller patch into the neighbour it shares the longest border with, the minimum mapping unit of a post-processing; a patch with no neighbour (a lone pixel of loss in a forest without a value around it) is left out. Simplify smooths the outlines to a tolerance in metres, their topology kept.

The Polygonize window over Janus: mapbiomas_grid [1985]: 2000 x 2000 px, 19 classes named; Neighbours 4 (sides) ticked, 8 (and corners); Minimum area 0.0 ha; Simplify 0 m; Mean of none; Name mapbiomas_grid [1985] polygons; Columns: value, class, area_ha (geodesic), perimeter_m, pixels; a GeoPackage opened as a vector layer; Run. Behind it the map of 1985 zoomed in, forest patches in dark green among pasture and mosaic, every patch outlined by its polygon in dark brown; pin 1 in a forest patch. The chart: pin 1 at forest until 1989, pasture from 1990; its Statistics: class at the date Forest Formation (3), last change 1990: Forest Formation (3) to Pasture (15).
1985 as polygons: every patch outlined, here around pin 1, a forest of 1985 that became pasture in 1990.

Polygonized whole, with the window's defaults (4 neighbours, no minimum area), MapBiomas' 1985 gave 38 582 polygons in 0.8 s, 259 296.86 ha in all, 10 784 of them forest. The forest's polygons add up to 36 895.005 ha, the Areas tab's count of its pixels to 36 895.008 ha: the polygons measured on the ellipsoid, the pixels by their interpolated areas, a few hundred square metres apart in 369 km². With 8 neighbours and a minimum area of 1 ha, the same function gave 11 089 polygons in 0.7 s, the same 259 297 ha in all.

The forest patches of 1985 of 100 ha or more were kept as a layer of their own, forest_1985.gpkg (with GDAL: value = 3 AND area_ha >= 100, the largest first): 49 patches, 13 399 ha, over a third of the forest of 1985. The largest, 910.9 ha, has a perimeter of 81.4 km and 10 369 pixels. The polygons follow the pixels' edges, and those on the window's western edge are cut by it.

Labels

A vector layer's menu has Labels: a column whose values are written on the map (here class), their size, colour and halo. A polygon's label goes at its point inside, a line's at the middle of its length, a point's to its right. Labels never cover each other: the larger features are labelled first, a label that would cover one already placed is left out, and a polygon smaller on screen than twice the label's height gets none, so zooming in brings the smaller patches' names. The labels are kept in the project, copied with Copy style, and drawn in the map's PNG and the animations.

The 1985 MapBiomas polygons over the classified map, each large polygon labelled with its class name in white with a dark halo, no two labels overlapping
Labels by class: the 38 582 polygons of 1985 labelled with class (--labels class). Zoomed out only the largest patches have room for a label; none covers another.

Large layers

The graphics card draws the vector layers, as a GIS does: each layer is uploaded once as levels of detail (each ring simplified with Douglas–Peucker, a ring smaller than a level's tolerance a dot), and a frame draws the coarsest level within half a pixel of the outlines, only where the map is. A state's rural properties are drawn whole at any zoom: a synthetic one of 1.2 million polygons and 34 million vertices (tools/bench_vector_draw.py) is read in 1.6 s, placed in 0.4 s, built for the GPU in 1.0–1.3 s (730 MB of GPU memory) and drawn whole about 3–4 s after it was opened; a frame then takes 4–9 ms with the whole layer in view, 1 ms 8 times closer, 0.4 ms zoomed in, and 97% of its outlines as GDAL rasterizes them are lit zoomed out (the CPU drew 56%, of a file cut at 20 million vertices). The feature under the cursor is found through a grid of the features’ boxes: 38 µs among the 1.2 million polygons, where it took 0.7 ms.

06Back to the grid

Polygons go the other way too. Rasterize on the series' grid..., in a vector layer's menu, burns its features on the active series' grid (its CRS, origin, pixel size and size; the features reprojected to it), through GDAL's rasterizer, the function the gdal_rasterize command calls:

Burn
1 inside, 0 outside (a mask), the feature's number (1, 2... in reading order), or an attribute: a number as it is, a text as classes numbered by name, their names and colours on the new layer. A later feature is burned over an earlier one.
Pixels
Those whose centre is inside, or every one touched.
The Rasterize on the series' grid window over Janus: forest_1985.gpkg (49 features) on landsat's grid: 2000 x 2000 px; Burn 1 inside, 0 outside (a mask) ticked, the feature's number (1, 2...), an attribute; Pixels their centre inside ticked, every one touched; Name forest_1985.gpkg raster; the note: opens as a layer, a Zeit tool can then run only where it is not 0; the status: under way, it opens as a layer when ready. Behind it the whole window of 2000 x 2000 pixels: the mask, 0 in blue, the 49 patches in orange with their outlines, most of them in the west and north.
The 49 patches as a mask on the Landsat series' grid: 1 inside (orange), 0 outside (blue).

The mask took under a tenth of a second (the log says 0.0 s) and has 152 511 pixels at 1: exactly the sum of the patches' pixels, every one of them forest in 1985, and the same, pixel for pixel, as gdal_rasterize -burn 1 on the same grid. The polygons came from the pixels' edges, so their centres burn them back as they were.

A Zeit tool can then run inside the patches only. Under Run on the raster, a tool's panel has an Only list when a layer on the series' grid is open: everywhere, or where X is not 0, X a rasterized vector layer or a class map. Its pixels at 0 or without a value are left out of the run, and have no value in the outputs.

The Run on the raster part of the Mann-Kendall trend panel: the scope Whole image, below it Only: everywhere, then the output folder with Change..., Estimating the run time..., the Run button, Runs in the background on every core, see Tools > Tasks, Preview and Auto-preview.
Only, under a tool's scope (here Mann-Kendall's, the mask open beside the Landsat series): everywhere, or where a layer on the grid is not 0.

07Zonal statistics over time

Zonal statistics over time..., in a vector layer's menu, makes each feature a zone and puts it through every date of the active series (or of a run's map). Its pixels are those whose centre is inside, or every pixel touched; a pixel in several features counts in each, as in rasterstats and exactextract. The features are burned once on the grid, then each date is read once, in blocks of rows on the processing threads.

Values
For each zone and date: the number of values, mean, standard deviation, least, percentiles 10, 25, 50, 75 and 90 (numpy's, linear) and largest.
Classes
Each class's pixels and area (each pixel's true area, as in Areas) and its share of the zone; with Transitions, from one date to another, the area of each class that became each other class.
Change dates
For a map of change years: each zone's area by year.
Zones named by
An attribute or the FID; Where: every feature, or those within the ROI.

The task writes zonal_long.csv (a row per zone, date and class or statistic), zonal_wide.csv (a row per zone, a column per date: the statistic chosen under Columns, or each class's hectares), transitions.csv when asked for, and a GeoPackage of the features with those columns added, opened as a layer. Analysis → Zonal draws a feature's series: its mean and median with the bands from the 10th to the 90th and the 25th to the 75th percentile, or its classes' areas stacked by date; it follows the feature picked in the attribute table.

The Zonal statistics over time window over Janus: forest_1985.gpkg: 49 features, each a zone; Map mapbiomas_grid (each date); What: values their statistics, classes their areas (ticked), change dates the area by year; Transitions from 1985 to 2018 unticked; Zones named by the FID; Pixels their centre inside; Where every feature; Name forest_1985.gpkg zonal; the status Done in 0.8 s. Behind it the MapBiomas map of 2018 with the 49 patches drawn over it, and the Analysis panel at its Zonal tab: forest_1985.gpkg on mapbiomas_grid, Feature 1, 10369 pixels, 0.8 s, the tables and the GeoPackage in the zonal results folder, the pixel areas' note; a bar per year from 1985 to 2018 of feature 1's classes, nearly all forest in dark green with a thin band of pasture and mosaic at the top from 1990, the legend of six classes over the last years.
The 49 patches through 34 years of MapBiomas, by class: feature 1, the largest patch, still forest almost whole.

The 49 patches through the 34 years took 0.8 s (no pixel in two patches here). Of their 13 399 ha of forest in 1985, 11 000 ha (82.1%) were forest in 2018; 1 442 ha had become pasture, 590 ha the mosaic of agriculture and pasture, 143 ha sugar cane. Patch by patch, the share still forest in 2018 goes from 99.3% to 37.2% (median 86.2%):

Patch (FID)1985 (ha)Forest in 2018 (ha)Still forestIts least forest2018, the rest
1, the largest910.9871.595.7%846.7 ha (1992)annual crops 19.8 ha, mosaic 12.9 ha, pasture 5.5 ha
11405.0174.143.0%173.9 ha (2008)pasture 188.2 ha, mosaic 42.7 ha
40128.947.937.2%46.9 ha (2006)water 40.8 ha, pasture 27.8 ha, mosaic 12.5 ha

Patch 11, where pin 1 stands, lost a quarter of its forest by 1990 (304.3 ha left) and kept losing it until 2008; it has stayed between 174 and 184 ha since. A third of patch 40 is River, Lake and Ocean in 2018. Set beside section 04, the patches tell what the area's total hid: the large forests of 1985 lost 2 399 ha, while the forest outside them, in smaller patches and in new ones, went from 23 496 to 23 894 ha.

08A series by zone

The same patches over the Landsat series (landsat active, What: values) give each patch's statistics of the vegetation index at each of the 41 years: the medoids are noisy pixel by pixel, a patch's median much less so.

Janus with three layers: forest_1985.gpkg (49 polygons) over landsat (active, 41 dates, 1985 to 2025, Float32) and mapbiomas_grid. The map of the index in 2018 from -0.07 (purple) to 0.43 (yellow), zoomed in, the 1985 forest patches outlined in dark brown over yellow and green; pin 1 in a patch at the top, pin 2 at the bottom. The chart, lines and markers: both pins between about 0.15 and 0.47, swinging from year to year, with their OLS trends dashed, pin 1's falling and pin 2's rising. The Statistics table: pin 1 mean 0.3002, median 0.31427, OLS trend -0.0016337 per year, largest drop 0.23196 (2008); pin 2 mean 0.34415, median 0.37101, OLS trend 0.0027158 per year, largest drop 0.22545 (1987).
The index under the patches in 2018: pin 1 (the pasture since 1990) and pin 2 (the forest) swing widely from year to year (ranges of 0.31 and 0.30); their trends part, −0.0016 and +0.0027 a year.

The median of each patch's pixels (from zonal_long.csv):

Patch (FID)198519952005200820182025
1 (95.7% still forest)0.3340.3320.3730.3340.3780.444
11 (43.0%)0.3730.3090.2900.2530.3030.341
40 (37.2%)0.3560.2760.2090.2010.2640.311

Patch 1, nearly all forest still, is the highest after 2005 and rises to 0.444 in 2025. The two patches that lost most of their forest fell from about 0.36 in 1985 to 0.20–0.25 by 2005–2008, the years MapBiomas gives their least forest; patch 11's lowest median is 0.234 (2015), patch 40's 0.092 (2017), when part of it was water. Both are higher in 2025: the series goes seven years past the last map of MapBiomas collection 4.1, and the same zones follow it there. The 49 patches through the 41 years took 24.2 s, most of it reading: each medoid is stored a row of the whole 7478-pixel scene per block, so every date reads whole rows of the scene for the window's 2000 columns.

09Along lines, and the ROI kept

A transect (Ctrl + drag, or Alt + click a line of a vector layer) shows one line at a time. Profiles of every line..., in a line layer's menu, puts every line of the layer through the series at once: each line, or each part of a multi-line, is sampled about a pixel apart along its length as the transect samples it (its length in pixels, rounded up, plus one samples, evenly spaced; distances in metres, on the sphere for longitudes and latitudes), at every date or those from one to another. It runs as a task (Tools → Tasks, with its progress and a cancel) on the processing threads, and reads only the windows of up to 128 × 128 pixels around the samples, each once a date: the values the chart shows.

The Profiles of every line window over Janus: lines.gpkg: 3 lines, on landsat; Dates every date (41) ticked, from 1985 to 2025; Fields name ticked; Tables a row per sample and date (long), and a row per sample, a column per date (wide) unticked; Name lines.gpkg profiles; the note: each line sampled about a pixel apart along its length, as the transect samples it, samples off the series left out, the values the chart shows; Threads 3 of 20; Run; the status Done in 7.5 s: 3 lines, 1471 samples, 41 dates (1428 pixels in 12 windows a date), 0 lines off the series, and the results folder. Behind it the index of 2018 zoomed in, from -0.07 (purple) to 0.43 (yellow), the three lines drawn over it, the first one in light blue as the transect. Below, the Transect panel: landsat 14.79 km, 2 bends, 496 samples, full resolution; the distance from A in metres against the dates from 1985 at the top to 2025, yellow where the index is high, the bends dashed. A notice: Done, Profiles of lines.gpkg, in 7 s.
Three lines through the Landsat series, all 41 years: the first one is also the transect below, the same 496 samples.

profiles.csv has a row per sample and date: fid, the fields ticked, part, sample, distance_m, x, y (in the series' CRS), col, row, date and value; samples off the series are left out. and a row per sample, a column per date also writes profiles_wide.csv. Here the three lines gave 1471 samples, 60 311 rows, and the mean of the index along each:

LineLengthSamples1985200820182025
through the patches14.79 km4960.2720.2450.2910.336
west to east12.40 km4230.2490.2160.2700.329
north to south16.44 km5520.2780.2030.2310.294

All three are at their lowest, or nearly, in 2008, the years the patches of section 07 had least forest; the line from north to south is still the lowest in 2018, and all three are higher in 2025.

The ROI is kept the same way. View → Edit ROI vertices (Shift+R) puts a square on each of its vertices and a dot in the middle of each edge: drag a vertex to move it, drag a dot to add a vertex there, right click a vertex (or Delete over it) to remove it. Each change is the ROI at once, its statistics read again, and a step of Edit → Undo. A polygon of a vector layer taken as the ROI with Alt + click is edited the same way, its file untouched: it is meant for adjusting an ROI before using it, not for editing a layer.

Janus with forest_1985.gpkg over landsat, zoomed in on patch 11, its outline in dark brown along the pixels. The ROI, the patch's outline simplified to 54 vertices, drawn in white with a white square at each vertex and a small dot in the middle of each edge. The chart: the ROI's mean between 0.25 and 0.38 from 1985 to 2025 with its band from the 10th to the 90th percentile; the Statistics of the ROI: 41 valid, mean 0.32515, OLS trend -0.00036449 per year, least 0.25182 (2008).
The ROI's vertices: patch 11's outline, simplified, ready to be moved, added to or removed.

View → Save ROI as vector... (also in the command palette) writes it as a GeoPackage, a GeoJSON or a shapefile, by the name's extension, in the active layer's CRS (a GeoJSON in WGS 84 when the CRS has no EPSG code, as RFC 7946 asks): one polygon, its holes kept, with name, layer and pixels. Opened as a vector layer, Alt + click on it gives back the same ROI, pixel for pixel; a project keeps the ROI as well.

The Save ROI as vector window: The ROI, a polygon of 54 vertices over 5536 pixels, in the active layer's CRS (WGS 84); the file landsat_roi.gpkg in the guide's poly folder, Browse...; Open it as a vector layer ticked; Save and Cancel.
Save ROI as vector: the file named after the active layer, in the export folder.

10Checked against the originals

Each tool was run against the program it stands in for, on the same files, comparing the results and the time (scripts tools/bench_*.py, results in bench/*.json; synthetic maps at 30 m, 8 threads where a tool takes them, GDAL 3.11.5):

JanusAgainstCaseJanusOriginalResults
Reprojectgdalwarp; rioxarray8192 × 8192 to EPSG:4326, bilinear, COG2.13 s2.09 s; 5.2 sbit for bit the command's
8192 × 8192 classes to 90 m by mode0.38 s0.39 s; 0.72 sbit for bit; rioxarray equal
Polygonizegdal_polygonize; rasterio.features.shapes2048 × 2048, 6 classes: 11 732 polygons1.15 s0.75 s; 0.67 sthe same polygons
4096 × 4096 years of loss: 45 711 polygons1.48 s1.43 s; 1.32 sthe same polygons
AreasR's terra::expanse8 maps of 4096 × 4096 in UTM0.39 s187 swithin 7 × 10⁻⁶
8 maps of 4096 × 4096 in degrees0.38 s1.1 swithin 2 × 10⁻⁹
Rasterizegdal_rasterize; rasterio.features.rasterize11 732 polygons on 2048 × 2048, Int320.68 s2.69 s; 2.6 sequal pixel for pixel
45 711 polygons on 4096 × 4096, Int320.29 s9.6 s; 1.6 sequal pixel for pixel
Zonal, valuesrasterstats2100 zones, 8 dates of 4096 × 40961.8 s62 sequal to single precision
Zonal, classesrasterstatsthe same, 5 classes1.3 s39 severy count equal
Profiles of every lineshapely + rasterio200 lines, 12 dates of 4096 × 4096: 2 822 100 values0.72 s3.6 s; 71 sthe same pixels and values
Save ROI as vectorgeopandas + rasterio.features.rasterize600 vertices and a hole: 5 146 615 pixels0.015 s0.020 sthe same pixels
Reproject
Janus's files equal the command's in all four cases (also a 60 km window to EPSG:3857 by cubic, 0.12 s against 0.14 s, and a warped VRT, 0.03 s against 0.06 s). rioxarray's bilinear differs by 3 × 10⁻⁴ at the median: rasterio's reproject does not widen the kernel when it reduces, as gdalwarp does.
Polygonize
gdal_polygonize writes the polygons and their value alone; Janus adds the geodesic area and perimeter of each. With the shapes measured by pyproj's Geod, rasterio took 2.0 s and 3.3 s. OGR's own geodesic area builds a transformation per polygon: measured that way, the first case took 13 s, against 1.2 s with all the rings transformed at once, as Janus does.
Areas
The pixel counts equal numpy's. A count times the nominal pixel area, what a script usually does, took 1.5 s; it is off by up to 0.007% in UTM, and in degrees it gives square degrees, not square metres. On a map of years of loss 8192 × 4096 crossing UTM's central meridian, terra took 373 s and gave 4 × 10⁻⁴ less; PROJ's area factor, pixel by pixel, gives Janus's value.
Rasterize
GDAL burns into a GeoTIFF through its block cache; Janus burns strips of rows in memory (256 MB at most) and writes them, 2 to 30 times faster. The values burned by the centres give back the map polygonized.
Zonal
2000 Voronoi cells and 100 circles over them, 327 975 pixels in more than one zone. Means within 1.7 × 10⁻⁷ and 90th percentiles within 2.5 × 10⁻⁶ of rasterstats' (single precision against double), counts, least, largest and deviations equal. R's exactextractr weights each pixel by the part of it inside: 20 s and 7 s, its means 1.9 × 10⁻⁴ (the median zone) to 2.5 × 10⁻³ (the 99th percentile) from the centres'.
Profiles
shapely's line_interpolate_point at the same distances along each line (in the grid's pixels), the pixel under each read by rasterio, each date whole and indexed with numpy (3.6 s) or with src.sample (71 s); Janus read 890 windows a date with 3 threads. Every row on both sides, the same pixel and the same float32 value; distances and places within 5 µm. The long CSV written in 0.77 s, by pandas in 9.9 s.
Save ROI as vector
The GeoPackage and the GeoJSON read back by geopandas (EPSG:32723, a polygon of 600 vertices with its hole) and burned by their pixels' centres on the grid: the ROI's own pixels, none more or less. geopandas wrote the GeoJSON in 0.003 s, as Janus did.

11What next

  • Adjusted areas: the Validation guide turns a map's areas into estimates with confidence intervals from a sample.
  • Trends inside the zones: the Trend test guide tests whether a class or a region of the same Landsat series is really greening or browning.
  • Change maps: LandTrendr's year of loss polygonized gets a date per polygon and, with Mean of, its magnitude; Area by value... and the zones' change dates give its hectares by year.
  • Expressions: the raster calculator combines the class map and the series, two series matched by year (the index where the year's class is 3, with where), once they share a grid.

12References

  • Souza, C. M., Jr., et al. (2020). Reconstructing three decades of land use and land cover changes in Brazilian biomes with Landsat archive and Earth Engine. Remote Sensing, 12(17), 2735. MapBiomas Project, collection 4.1 of the annual land use and land cover maps of Brazil, mapbiomas.org.
  • GDAL: gdalwarp, gdal_polygonize, gdal_rasterize.
  • Karney, C. F. F. (2013). Algorithms for geodesics. Journal of Geodesy, 87(1), 43–55. (geographiclib: the geodesic areas and perimeters.)
  • Hijmans, R. J. terra: Spatial Data Analysis, R package (expanse).
  • Perry, M. rasterstats: summarize geospatial raster datasets based on vector geometries, Python package.
  • Baston, D. exactextractr: fast extraction from raster datasets using polygons, R package.