- How
expressioncomputes a new map, pixel by pixel. - How a widget in
descriptionHtmlbecomes an input of the calculation. - Why the same input is read by id in
beforeCalcand by position inexpression.
A calculated layer (source: "calculate") does not show a published map as it is: it reads the values of the maps in its name - an elevation in metres, a biomass in tonnes - and an expression turns them into a new map. Controls placed in the layer’s panel (sliders, lists, text boxes) are inputs of that calculation.
The value of a pixel is read from the map’s legend: the elevation map has a class every few metres, so a pixel’s value is the class it falls in. Maps published as raw maps also give the exact cell value, with operation: "raw" on the layer.
The query 25 lines · runs as is
// A calculated map with a control: a slider feeds the expression, and moving it computes the map again.
// beforeCalc runs on the page before each calculation: there the inputs are read by id.
// expression runs in a separate worker, once per pixel: there the inputs are read by position
// (inputs[0] is the first input in descriptionHtml) - see the execution model.
[
{
title: "Land above an elevation",
name: "CSR:altimetria",
source: "calculate",
visibility: true,
opacity: 0.85,
paramsButtonConfig: [{ type: "query", pressed: true }], // the panel with the controls starts open
descriptionHtml:
"{{label|text=Minimum elevation (m)}}" +
"{{slider|id=minimum|minValue=0|maxValue=2000|increment=100|value=1000|width=250}}",
beforeCalc: function (inputs) {
this.setCalculateLegend([
{ color: [192, 57, 43], value: 1, title: "Above " + inputs.id["minimum"] + " m" },
]);
},
expression: function (layersVals, inputs) {
return layersVals[0] >= inputs[0] ? 1 : this.nullValue;
},
},
];The cycle
- The panel:
descriptionHtmlholds text and widgets, written in a small markup -{{slider|id=minimum|...}}is a slider whose value is the inputminimum. - The reader moves the slider. After a short countdown (so several changes make one calculation) the layer is calculated again. With
updateAutomatically: trueon the layer it is calculated at once, with no countdown. beforeCalc(inputs)runs first, on the page: here it sets the legend withthis.setCalculateLegend([...]). On the page, an input is read by its id:inputs.id["minimum"].expression(layersVals, inputs)runs for every pixel, in a separate worker, so the page stays responsive.layersVals[0]is the value of the first map innameat that pixel. There, inputs are read by position:inputs[0]is the first input of the panel. The value it returns is coloured by the legend;this.nullValueleaves the pixel transparent.
Because expression runs in the worker, it only sees its two arguments: a variable or function of the page is not there. Values it needs come in as inputs.
The legend title carries the slider’s value. Besides telling the reader what is shown, a legend that changes is what makes the map redraw - see the pitfalls.
Yours, or the layer’s
beforeCalc and expression are functions you write; the platform calls them. this.setCalculateLegend is a function the layer already has; you call it. The API lists them apart: Layer callbacks for the first, Calculated layers: methods for the second.
paramsButtonConfig: [{ type: "query", pressed: true }] presses the layer’s query button at the start, so its panel opens with the slider. Only calculated, file and tile layers have this panel; the row of a published map shows its legend and styles instead.
Try this
- Drag the slider: after a short countdown the map is computed again, and the legend follows.
- Change the legend colour to green,
[39, 174, 96]- colours are[R, G, B]lists here, not CSS strings. - Use two maps:
name: "CSR:altimetria,CSR:bioma_cerrado_declividade"gives each pixel two values,layersVals[0]andlayersVals[1]- see the two-slider example of the Slider page.
Read more
- Execution model - the full cycle of a calculation
- Expression
- Slider - and every other input widget in the tools
- The widget markup language