In this step you learn
  • How expression computes a new map, pixel by pixel.
  • How a widget in descriptionHtml becomes an input of the calculation.
  • Why the same input is read by id in beforeCalc and by position in expression.

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.

Click the picture to run it live - nothing is saved.
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

  1. The panel: descriptionHtml holds text and widgets, written in a small markup - {{slider|id=minimum|...}} is a slider whose value is the input minimum.
  2. The reader moves the slider. After a short countdown (so several changes make one calculation) the layer is calculated again. With updateAutomatically: true on the layer it is calculated at once, with no countdown.
  3. beforeCalc(inputs) runs first, on the page: here it sets the legend with this.setCalculateLegend([...]). On the page, an input is read by its id: inputs.id["minimum"].
  4. 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 in name at 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.nullValue leaves 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

  1. Drag the slider: after a short countdown the map is computed again, and the legend follows.
  2. Change the legend colour to green, [39, 174, 96] - colours are [R, G, B] lists here, not CSS strings.
  3. Use two maps: name: "CSR:altimetria,CSR:bioma_cerrado_declividade" gives each pixel two values, layersVals[0] and layersVals[1] - see the two-slider example of the Slider page.

Read more