{
  "Layers": {
    "_information": {
      "title": "Layers: what you write in a layer",
      "description": "A layer is one entry of the query's list, between { }. Its source sets its kind: no source (a published map), \"calculate\", \"file\" or \"xyz\". The groups below are the keys and functions you write in a layer, and the buttons of its row.\nNot every key works on every kind: calculated, file and tile layers have a panel (descriptionHtml) and row buttons (paramsButtonConfig), a published map does not. Each entry says when it is limited to some kinds; what one kind adds is under Kinds of layer.\nA layer that shows one published map:",
      "example": "[\n   {\n      // The Layer name\n      title: 'My first layer!',\n\n      // The map that will be shown\n      name: 'CSR:altimetria',\n\n      // Flag to make the map show on start\n      visibility: true,\n   },\n]"
    },
    "LayersProperties": [
      {
        "key": "_information",
        "description": "Keys of a layer object: which map it shows (name, source, styles), how it looks (title, opacity, visibility) and how its row and panel behave.",
        "name": "Layer properties"
      },
      {
        "key": "categorical",
        "description": "Switches the generated legend of a `source: 'calculate'` layer to categorical mode.\nWhen `true`, every distinct value returned by `expression()` becomes its own legend entry (values are\ngrouped by value, never into ranges) and the colours come from `categoricalPalette`. When `false`\nthe values are grouped into at most `maxQntEntries` ranges coloured with the sequential\n(blue to red) palette. Use it when the expression returns classes (land use, geology, ...) instead\nof a continuous number.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'Categorical result',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'One legend entry per geology class',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           updateAutomatically: true,\n           categorical: true,\n           expression: function(layersVals, inputs) {\n              return layersVals[0];\n           },\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "categoricalPalette",
        "description": "Colours of the legend entries of a categorical `source: 'calculate'` layer (see `categorical`),\nas an array of `[R, G, B]` triplets (0 to 255). The entries are assigned, in the sorted order of the\nvalues returned by `expression()`, spreading evenly over the palette. When omitted a built-in\n300-colour palette is used. The palette length is also the upper limit for `maxQntEntries`.",
        "type": "Array.<Array.<Number>>",
        "examples": [
          "[\n  {\n     title: 'Custom categorical colours',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Geology classes with a four colour palette',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           updateAutomatically: true,\n           categorical: true,\n           categoricalPalette: [[43, 3, 192], [126, 222, 20], [243, 99, 6], [34, 67, 2]],\n           expression: function(layersVals, inputs) {\n              return layersVals[0];\n           },\n        },\n     ],\n  },\n]"
        ],
        "default": "300 built-in colours"
      },
      {
        "key": "description",
        "description": "Define the text that will be displayed when the mouse hover the Legend Window Title.\nThis description applies when the 'source' of the map is 'xyz' or the Layer is a 'vector'.",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Example of description for XYZ map',\n     color: '#666699',\n     elements: [\n        {\n           title: 'This is the Layer Legend Window Title, hover me!',\n           name: 'planet:planet',\n           source: 'xyz',\n           url: 'https://tiles.planet.com/basemaps/v1/planet-tiles/planet_medres_visual_2021-09_mosaic/gmap/${z}/${x}/${y}.png?api_key=PLAK78456687760442eaa3d3da16aaac5f2d',\n           visibility: true,\n           description: 'Hover the Layer Title',\n        },\n     ],\n  },\n]"
        ],
        "default": "'XYZ Title' or 'Vector Title'"
      },
      {
        "key": "descriptionHtml",
        "description": "Define the content that will be displayed at the Query section (\"Exibir Consulta\" button).\nThis property accepts any string in HTML format. Besides that, you can also use predefined tools.\nOnly calculated, file and tile (xyz) layers have this panel; on a published map (no `source`,\n`source: 'local'` or a server added with `addRemoteWMSServer`) it is ignored.",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Example of descriptionHTML',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This Layer has a custom descriptionHTML',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           paramsButtonConfig: [\n              {\n                 type: 'query',\n                 pressed: true\n              },\n           ],\n           descriptionHtml:\n              '<p>You can use HTML tags</p>'\n              +\n              '{{label|text=Or the tools from Mappia:}}'\n              +\n              '{{button|text=I\\'m a button from Mappia}}',\n        },\n     ],\n  },\n]"
        ],
        "default": "string.empty",
        "see": [
          "You can find more information about the tools available for use in: [Tools Section](#group_Tools)."
        ]
      },
      {
        "key": "disabledAttributes",
        "description": "Define which styles will be hidden in the Style Chooser Combobox from Layers that has the 'source' property as 'local'.\nYou need to pass the name of a style as in the GetCapabilities() xml. They follow the pattern of the map name, underline (_) and then the number of the style. (<map_name>_<number>).\nYou can hide more than one style. For this you only need to separate their names by commas.",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Example of disabledAttributes',\n     color: '#666699',\n     elements: [\n        {\n           title: 'This Layer hides the styles \\'Mapa geológico\\' and \\'Tempo geológico\\' from the styles choose combobox',\n           name: 'CSR:geologia',\n           source: 'local',\n           visibility: true,\n           // geologia_2 is the style 'Tempo geológico'\n           // geologia_1 is the style 'Mapa geológico'\n           // The 'disabledAttributes' property is used to hide the styles from the styles chooser combobox\n           // All styles that are hidden must be separated by commas\n           disabledAttributes: 'geologia_2,geologia_1',\n        },\n     ],\n  },\n]"
        ],
        "default": "null"
      },
      {
        "key": "exclusiveGroupDetails",
        "description": "Define a name for a Layer Details. Between all Layers with the same ‘exclusiveGroupDetails’ name, only one Layer can have its Legend open each time.",
        "type": "String",
        "examples": [
          "[\n  {\n     viewTitle: 'Only one Legend can be open each time',\n     title: 'Example of exclusiveGroupDetails',\n     color: '#666699',\n     elements: [\n        {\n           title: 'Layer 1',\n           name: 'CSR:estados',\n           source: 'calculate',\n           exclusiveGroupDetails: 'ExampleName',\n           visibility: true,\n        },\n        {\n           title: 'Layer 2',\n           name: 'CSR:rios_principais',\n           source: 'calculate',\n           exclusiveGroupDetails: 'ExampleName',\n           visibility: true,\n        },\n        {\n           title: 'Layer 3',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           exclusiveGroupDetails: 'ExampleName',\n           visibility: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "string.empty"
      },
      {
        "key": "extents",
        "description": "Define which tiles will be used to render the map. All the tiles that are within the 'extents' coordinates will be used to render the map.\nThe extents must be in EPSG:4326 (coordinates in lat,long). Also, the array values need to be in the order: [minX, minY, maxX, maxY].",
        "type": "Array.<Numeric>",
        "examples": [
          "// Play around with the maps visibility to check the regions defined by the extents.\n[\n  {\n     title: 'Example of extents',\n     color: '#FFA900',\n     elements: [\n        {\n           title: 'Restricted extents',\n           name: 'CSR:altimetria',\n           visibility: true,\n           // Follows the order [minX, minY, maxX, maxY]\n           extents: [-48.5, -14.5, -46.0, -13.0],\n        },\n        {\n           title: 'Restricted extents 2',\n           name: 'CSR:altimetria',\n           visibility: true,\n           // Follows the order [minX, minY, maxX, maxY]\n           extents: [-43.9, -19.8, -43.1, -18.9],\n        },\n        {\n           title: 'This Layer displays the hole map',\n           name: 'CSR:altimetria',\n           // This extents render the whole world.\n           extents: [-180.0000, -90.0000, 180.0000, 90.0000],\n           startListed: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "Array.empty"
      },
      {
        "key": "global",
        "description": "A second way to declare query globals from a layer: an object whose properties\nbecome temporary globals, passed to `ExtjsUtils.QUERY.setQueryGlobalProperties`\nwhile the layer is interpreted. The usual form is the\n`ExtjsUtils.QUERY.setQueryGlobalProperties({...}) && [...]` chain at the top of\nthe query; the production survey found no query using this key (zero users).",
        "type": "Object",
        "examples": [
          "[\n  {\n     title: 'Group',\n     elements: [\n        {\n           title: 'Layer with its own globals',\n           name: 'CSR:estados',\n           source: 'local',\n           visibility: true,\n           global: {\n              statesLoaded: function() { ExtjsUtils.ALERTIFY.log('States ready'); }\n           }\n        }\n     ]\n  }\n]"
        ]
      },
      {
        "key": "hideLegendButton",
        "description": "Hides the legend of the layer in its Legend Window. Set it to 'true' to hide it, 'false' to display it.\nIt applies to every layer source: on a `local` layer it hides the \"show legend\" toggle button of the row;\non a `calculate`, `file` or `xyz` layer it hides the generated legend container (with the legend title\nand the opacity slider) and the \"show legend\" button at the bottom of the query section, so only the\n`descriptionHtml` remains.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'Hiding the Legend Button',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'The Layer Legend Button will be hidden',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           hideLegendButton: true,\n           descriptionHtml:\n              '{{label|text=The legend button is hidden, only the descriptionHtml is visible}}',\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "hideListing",
        "description": "Hides the layer from its group's menu at the top of the screen: the list that appears\nwhen the mouse is over the group title. Set it to `true` to leave the layer out of that\nmenu; `false` (the default) lists it.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'A Group with 3 Layers, but one is hidden',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Map of the altitude in Brazil',\n           name: 'CSR:altimetria',\n           source: 'local',\n        },\n        {\n           title: 'This Layer will be hidden at the Group list',\n           name: 'CSR:rios_principais',\n           source: 'local',\n           hideListing: true,\n        },\n        {\n           title: 'Map of the geology in Brazil',\n           name: 'CSR:geologia',\n           source: 'local',\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "hideMetadata",
        "description": "If true, hides the metadata button in the Legend Window for this layer.\nThe button is also hidden when the link has `options=hidemetadata`: either one hides it, and\n`hideMetadata: false` cannot bring it back. Calculated, file and tile (xyz) layers only have\nthis button when their `paramsButtonConfig` has a `{type: 'metadata'}` entry.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'Hiding the metadata button',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This Layer has no metadata button',\n           name: 'CSR:geologia',\n           source: 'local',\n           visibility: true,\n           hideMetadata: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "hideStyleChooser",
        "description": "Define if should hide the Combobox with the map possible styles that can be applied to a map in a Layer that has the 'source' 'local'.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'Example with hidden style Combobox',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Map of the geology in Brazil',\n           name: 'CSR:geologia',\n           source: 'local',\n           visibility: true,\n           startLegendOpen: true,\n           hideStyleChooser: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "hideTilePlaceholder",
        "description": "Removes the stretched placeholder tile that is shown while the real tiles load (the OpenLayers\n`resize` transition). Set it to `true` for layers whose content changes between loads, such as\ntimeline or calculated maps, to avoid the previous image being stretched before the new one\nappears. Applies to `local` and `calculate` layers; a composed layer also passes it on to the\nmaps replaced with `changeLayers`.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'No stretched tiles while loading',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Map of the geology in Brazil',\n           name: 'CSR:geologia',\n           source: 'local',\n           visibility: true,\n           hideTilePlaceholder: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "insideOpacity",
        "description": "Defines the opacity for the maps defined in the 'name' property.\nThis property is an array of values which are the opacity that will be applied in the same order that the map was defined in the 'name' property. For example, the first value in the 'insideOpacity' array will define the opacity for the first map defined in the 'name' property, the second value is the opacity for the second map, and so on.\nThe opacity value is a number between 0 and 1. 0 being transparent and 1 opaque.",
        "type": "Array.<Numeric>",
        "examples": [
          "[\n  {\n     title: 'Example of insideOpacity',\n     color: '#666699',\n     elements: [\n        {\n           name: 'CSR:estados,CSR:rios_principais,CSR:geologia',\n           source: 'calculate',\n           // The values are applied to the layers in the same order as they are defined in the name property. So the CSR:estados map has the opacity of 0.5, the CSR:rios_principais map has the opacity of 1, and the CSR:geologia map has the opacity of 0.7.\n           insideOpacity: [0.5, 1, 0.7],\n           visibility: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "[1, 1, 1, ...]"
      },
      {
        "key": "legendTitle",
        "description": "Defines the text that will be displayed at the top of the 'legendhtml tool' declared at the 'descriptionHtml' property",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Example custom legend title',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Map of the geology in Brazil',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           legendTitle: 'This is a custom legend title',\n           descriptionHtml:\n              '{{legendhtml}}',\n        },\n     ],\n  },\n]"
        ],
        "default": "string.empty"
      },
      {
        "key": "maxQntEntries",
        "description": "Maximum number of legend entries generated from the values returned by `expression()` on a\nnon-categorical `source: 'calculate'` layer; when there are more distinct values than this they are\ngrouped into value ranges. The value is clamped to the length of the active palette.\nPS: the automatic grouping currently always uses the built-in limit of 18, so a custom value is kept\non the layer but does not change the generated legend. Use `setCalculateLegend()` when you need full\ncontrol over the legend entries.",
        "type": "Number",
        "examples": [
          "{\n   name: 'CSR:altimetria',\n   source: 'calculate',\n   maxQntEntries: 10,\n   expression: function(layersVals, inputs) {\n      return layersVals[0];\n   },\n}"
        ],
        "default": "18"
      },
      {
        "key": "name",
        "description": "Defines the maps that will be a part of the Layer. Every map defined in here will be shown when the Layer is active. Besides that, all of their information can be used to calculate a new map combining their data.\nIn this website you will find some of the maps that can be used: http://maps.csr.ufmg.br/geonetwork/srv/por/search.\nYou can find their name at the section \"Visão geral do mapa\" under its image. Those maps have the prefix \"CSR:\" before their names.\nMaps can also be uploaded by you. For that, you just need to define the 'source' property as 'file'.",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'A Group with two maps',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This Layer has two maps!',\n           // Each map is separated by commas\n           // The first map is CSR:rios_principais that have the rivers of Brazil\n           // The second map is CSR:estados that show the states of Brazil\n           name: 'CSR:rios_principais,CSR:estados_cf',\n           visibility: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "string.empty"
      },
      {
        "key": "opacity",
        "description": "Define the opacity of the Layer. The opacity is a value between 0 and 1 that is a scale for its transparency. 0 is completely transparent. 1 is completely visible.",
        "type": "Numeric",
        "examples": [
          "[\n {\n   title: 'Example of opacity in a Layer',\n   color: '#666699',\n   elements: [\n     {\n       title: 'This Layer has its opacity set to 50% of the original visibility',\n       name: 'CSR:geologia',\n       source: 'local',\n       visibility: true,\n       opacity: 0.5,\n     },\n   ],\n },\n]"
        ],
        "default": "1"
      },
      {
        "key": "otherNames",
        "description": "Defines additional maps to be available for the query, which will be listed in the filtered stored capabilities.\nEvery map included in layer definition needs to be either in 'name' property or 'otherNames' property.\n\nPS: This property must be used along with the 'capabilities' url option, which filters the 'GetCapabilities' response with only the maps included in layer definition. (Performance Optimization, faster loading).\nPS2: This property is only needed outside of the editor mode.",
        "type": "String",
        "examples": [
          "{\n     name: \"CSR:estados,CSR:airports\",\n     otherNames: \"CSR:municipios,CSR:rodovias\",\n     beforeCalc: function(layerVals, inputVals) {\n         this.changeLayers([{name: 'CSR:municipios', styles: fMapName + \"_1\", index: 0},\n             {name: 'CSR:rodovias', styles: 'rodovias_1', index: 1}]);\n     }\n  }\n\n  Function 'changeLayers' needs the definition of the 4 maps: 'estados', 'airports', 'municipios' and 'rodovias'.\n  If the 'otherNames' is not defined, only the maps defined at 'name' property will be available.\nApplies to calculated layers (`source: 'calculate'`), the ones that swap maps with `changeLayers`."
        ]
      },
      {
        "key": "priority",
        "description": "Defines the order of which map will be rendered on top of the others. Layers with higher 'priority' will always be rendered on top of the layers with lower 'priority'.",
        "type": "Number",
        "examples": [
          "[\n  {\n     title: 'At the bottom will be the geology, then the states and at top the rivers',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Map of the states in Brazil',\n           name: 'CSR:estados',\n           source: 'local',\n           priority: 2,\n           visibility: true,\n        },\n        {\n           title: 'Map of the rivers in Brazil',\n           name: 'CSR:rios_principais',\n           source: 'local',\n           priority: 3,\n           visibility: true,\n        },\n        {\n           title: 'Map of the geology in Brazil',\n           name: 'CSR:geologia',\n           source: 'local',\n           priority: 1,\n           visibility: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "0"
      },
      {
        "key": "showLoadingModal",
        "description": "Shows the \"generating legend\" loading mask visibly over the page while the layer is paused\n(calculating or loading a resource). By default the mask is an invisible overlay that only changes\nthe cursor to \"wait\"; set it to `true` to make it visible. Only shown while the layer is visible.\nApplies to `calculate` and `file` layers.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'Visible loading mask',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'A slow calculation with a visible loading mask',\n           name: 'CSR:altimetria',\n           source: 'calculate',\n           visibility: true,\n           updateAutomatically: true,\n           showLoadingModal: true,\n           expression: function(layersVals, inputs) {\n              return layersVals[0];\n           },\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "showRemoveBtn",
        "description": "Defines it the remove button at the top right of the Layer Legend Window show be displayed. Set it to 'true' to show it. Set it to 'false' to hide it.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'Hiding the remove button',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'The \\'X\\' button at the top right of this Layer removes this map',\n           name: 'CSR:rios_principais',\n           source: 'local',\n           visibility: true,\n        },\n        {\n           title: 'There is no button to remove the this map',\n           name: 'CSR:estados',\n           source: 'local',\n           visibility: true,\n           showRemoveBtn: false,\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "source",
        "description": "Defines the source from where the maps declared at the name will be loaded from. The Source can be one of the following:\n   - 'local': The maps in the name will be loaded from the CSR servers\n\n   - 'calculate': The maps in the name gonna be used to calculate a new map in the expression() function and the result will be displayed as the Layer\n\n   - 'file': The map will be loaded from a file or other custom source. For this, you need to define the file type in the 'type' property and the file where the map should be loaded. For example, for a JSON file, you also need to define the 'json' property.\n\n   - 'xyz': The map will be loaded from an url by the XYZ protocol. The url property is required for this type of source. You can find more informations about the XYZ protocol at: https://developers.planet.com/docs/basemaps/tile-services/xyz/",
        "type": "String",
        "examples": [
          "// Example of how to load a map with the local source\n[\n  {\n     title: 'Map rendered by a local source',\n     color: '#666699',\n     elements: [\n        {\n           title: 'Map with local source',\n           name: 'CSR:paises',\n           source: 'local',\n           visibility: true,\n        },\n     ],\n  },\n]",
          "// Example of how to load a map with the calculated source\n[\n  {\n     title: 'Map rendered based on calculation',\n     color: '#666699',\n     elements: [\n        {\n           title: 'Filtering a part of the map',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           updateAutomatically: true,\n           expression: function(layersVals, inputs) {\n              // layerVals have the value of the legend applied in every pixel of the map\n              let mapValue = layersVals[0];\n\n              // Remove every type of relief that is not 'Mantiqueira'\n              if(mapValue != 'Mantiqueira') { // If the pixel is not 'Mantiqueira'\n                 return undefined; // Don't show the pixel\n              }\n\n              // If the pixel is 'Mantiqueira', return the value of the pixel\n              return mapValue;\n           },\n        },\n     ],\n  },\n]",
          "// Example of how to load a map with the file source\n[\n  {\n     title: 'Map rendered based on file source',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Map with file source',\n           name: 'example:map_file_source',\n           source: 'file',\n           visibility: true,\n           // The map is rendered based on the GeoJSON below\n           // The GeoJSON needs to be in EPSG:3857\n           json: JSON.stringify(\n              {\n                 \"type\": \"Polygon\",\n                 \"crs\": {\n                    \"type\": \"name\",\n                    \"properties\": {\n                       \"name\": \"EPSG:3857\"\n                    }\n                 },\n                 \"coordinates\": [\n                    [\n                       [\n                          -625570.6,\n                          6465993.0,\n                       ],\n                       [\n                          -305006.9,\n                          6696510.8,\n                       ],\n                       [\n                          -546330.2,\n                          6768547.6,\n                       ],\n                       [\n                          -445478.6,\n                          7053093.0,\n                       ],\n                       [\n                          -279794.0,\n                          7056694.8,\n                       ],\n                       [\n                          -359034.5,\n                          7337638.4,\n                       ],\n                       [\n                          -211359.0,\n                          7485313.8,\n                       ],\n                       [\n                          62380.8,\n                          6955843.3,\n                       ],\n                       [\n                          220861.7,\n                          6909019.4,\n                       ],\n                       [\n                          91195.5,\n                          6700112.6,\n                       ],\n                       [\n                          213658.1,\n                          6682103.4,\n                       ],\n                       [\n                          -625570.6,\n                          6465993.0,\n                       ],\n                    ],\n                 ],\n              }\n           ),\n        },\n     ],\n  },\n]",
          "// Example of how to load a map with the XYZ source\n// The XYZ source is used to load a map from a URL that contains the &lcub;z&rcub;, &lcub;x&rcub; and &lcub;y&rcub; placeholders\n// The &lcub;z&rcub; is the zoom level, &lcub;x&rcub; is the longitude and &lcub;y&rcub; is the latitude\n[\n  {\n     title: 'Map rendered by a XYZ source',\n     color: '#666699',\n     elements: [\n        {\n           title: 'Base map loaded by XYZ source',\n           // The name of the map that will be added. This name must be unique\n           name: 'planet:planet',\n           source: 'xyz',\n           // The url of the map that will be loaded. The $&lcub;z&rcub;, $&lcub;x&rcub; and $&lcub;y&rcub; placeholders will be replaced by the map library\n           url: 'https://tiles.planet.com/basemaps/v1/planet-tiles/planet_medres_visual_2021-09_mosaic/gmap/$&lcub;z&rcub;/$&lcub;x&rcub;/$&lcub;y&rcub;.png?api_key=PLAK78456687760442eaa3d3da16aaac5f2d',\n           visibility: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "'local'"
      },
      {
        "key": "startLegendOpen",
        "description": "Defines if the Legend Window should start or not. Set it to 'true' to make it start open. Set it to 'false' for it to start closed.\nApplies to published maps only (no `source`, `source: 'local'` or a server added with `addRemoteWMSServer`).\nCalculated, file and tile (xyz) layers ignore it: their details already start open, and\n`paramsButtonConfig: [{type: 'query', pressed: true}]` makes them start on the layer's panel instead.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'The Legend Window the Layer will start open',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This Layer Legend Window will start open',\n           name: 'CSR:geologia',\n           source: 'local',\n           startLegendOpen: true,\n           visibility: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "startListed",
        "description": "Define if the Layer should start with its Legend Window visible or not. This does not make the Layer visible on the map by itself. For that, you need to set the 'visibility' property to 'true'.\n'startListed' just displays the Legend Window associated with the Layer.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'The Legend Window of the third Layer will start visible',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Map of the altitude in Brazil',\n           name: 'CSR:altimetria',\n           source: 'local',\n        },\n        {\n           title: 'Map of the rivers in Brazil',\n           name: 'CSR:rios_principais',\n           source: 'local',\n        },\n        {\n           title: 'This Layer Legend Window will start visible, but is not map',\n           name: 'CSR:geologia',\n           source: 'local',\n           startListed: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "styles",
        "description": "Defines which styles will be applied over the maps defined at the 'name' property.\nThe styles are applied in the same order as the maps are declared in name. For example, if there are 3 maps in the 'name' property and 3 styles. The first style will be applied to the first map, the second to the second map, and so on.\nA style's Mappia identifier is the map name without its namespace plus `_` and an\nindex (`estados` published with two styles gives `estados_0` and `estados_1`), and the\nstyles a map really offers are the ones its published capabilities declare: open the\nlayer's style chooser in the interface to see and pick them, or read the `Style` entries\nof the map in the WMS capabilities. Styles are created when the map is published (a QGIS\nstyle file is converted at publication time), not by the query.\nPS: Styles are separated by comma, one per map in `name`, in the same order.",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Example of style',\n     color: '#666699',\n     elements: [\n        {\n           name: 'CSR:precip_monthly_average,CSR:precip_monthly_average',\n           // The first style 'precip_monthly_average_1' is applied to the first map 'precip_monthly_average' and the second style 'precip_monthly_average_2' is applied to the second map 'precip_monthly_average'\n           styles: 'precip_monthly_average_1,precip_monthly_average_2',\n           // The source needs to be calculate to apply the style\n           source: 'calculate',\n           visibility: true,\n        },\n     ],\n  },\n]",
          "{\n       ...,\n         name: 'CSR:municipios',\n         style: 'municipios_0',\n       ...\n  }",
          "{\n       ...,\n         name: 'CSR:estados,CSR:geologia',\n         style: 'estados_0,geologia_1',\n       ...\n  }"
        ],
        "default": "string.empty"
      },
      {
        "key": "thumbUrl",
        "description": "Define the thumbnail image that is displayed when the mouse hovers the Legend Window title.\nYou can define any image you would like to display as the map thumbnail. You just need to pass its URL.",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Example of thumbUrl',\n     color: '#FFA900',\n     elements: [\n        {\n           title: 'There is a puppy hidden in here! Hover me! <3',\n           name: 'CSR:altimetria',\n           visibility: true,\n           thumbUrl: 'https://fastly.picsum.photos/id/237/200/300.jpg?hmac=TmmQSbShHz9CdQm0NkEjx1Dyh_Y984R9LpNrpvH2D_U',\n        },\n     ],\n  },\n]"
        ],
        "default": "undefined"
      },
      {
        "key": "title",
        "description": "Defines the Title that will be displayed at the Legend Window in the top left corner of the screen and inside the Group List at the top center of the screen.",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'The title of the Group shows up at the top center of the screen and has a List inside it.',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'The title of the Layer is shown at the Legend Window and inside the menu of the Group name.',\n           name: 'CSR:capitals',\n           visibility: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "'layer_name' The default value is the layer name."
      },
      {
        "key": "updateAutomatically",
        "description": "Decides what happens when the reader changes an input of the layer's panel (a widget in `descriptionHtml`).\n`true`: the layer is calculated again at once. `false` (the default): a short countdown starts, with a button to\nrefresh now, so several changes make one calculation. The layer is drawn either way.\nApplies to calculated and file layers.",
        "type": "Boolean",
        "examples": [
          "[\n {\n   title: 'The map will be automatically calculated',\n   color: '#666699',\n   elements: [\n     {\n       title: 'This layer is showing just one type of relief',\n       name: 'CSR:geologia',\n       source: 'calculate',\n       visibility: true,\n       updateAutomatically: true,\n       expression: function(layersVals, inputs) {\n         // layerVals have the value of the legend applied in every pixel of the map\n         let mapValue = layersVals[0];\n\n         // Remove every type of relief that is not 'Mantiqueira'\n         if(mapValue != 'Mantiqueira') { // If the pixel is not 'Mantiqueira'\n             return undefined; // Don't show the pixel\n         }\n\n         // If the pixel is 'Mantiqueira', return the value of the pixel\n         return mapValue;\n       },\n     },\n   ],\n },\n]"
        ],
        "default": "false"
      },
      {
        "key": "viewColor",
        "description": "HTML color to use on the group.",
        "type": "String",
        "examples": [
          "[{\nviewColor: \"#FFFFFF\",\nviewTitle: \"DADOS BASE\",\ncolor: \"#FF000F\",\nelements: [\n  {name: 'CSR:AMAZONIA_limite', showRemoveBtn: false, visibility: true,\n    hideListing: true, startListed: true, priority: 10000}\n]}]"
        ]
      },
      {
        "key": "visibility",
        "description": "Define if the Layer should start visible or not. \nSet it to 'true' for the Layer start visible. Otherwise, set it to 'false' and the Layer will start hidden.",
        "type": "Boolean",
        "examples": [
          "[\n {\n   title: 'Example of visibility in a Layer',\n   color: '#666699',\n   elements: [\n     {\n       title: 'This layer will start visible',\n       name: 'CSR:geologia',\n       source: 'local',\n       visibility: true,\n     },\n   ],\n },\n]"
        ],
        "default": "false"
      }
    ],
    "ConfigLayer": [
      {
        "key": "_information",
        "completeAPI": "View all layer configuration <a href='/api/#category_ConfigLayer'>API here</a>.",
        "description": "More keys of the same layer object: scale limits, grouping (group, toggleGroup, openGroup), the row's layout and, on calculated layers, how each map is read (operation).",
        "name": "More layer properties"
      },
      {
        "key": "attribution",
        "description": "Recognizes someone as the platform author, showing in the \"Powered by: {insert name}\".",
        "type": "String"
      },
      {
        "key": "customLayerClass",
        "description": "Extra CSS class added to the row of the layer in the Legend Window and to its vertical options\nmenu (see `verticalMenu`), so the layer can be styled with `ExtjsUtils.CSS.defineClass` or a\nstylesheet. The same key on a `viewTitle` group is added to the group node instead\n(`GroupProperties.customLayerClass`).",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Styled layer row',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This row has a custom background',\n           name: 'CSR:geologia',\n           source: 'local',\n           visibility: true,\n           customLayerClass: 'highlighted-layer',\n        },\n     ],\n  },\n]"
        ],
        "default": "string.empty"
      },
      {
        "key": "group",
        "description": "Only the value \"background\" does anything: it makes the layer a basemap, drawn under every other\nlayer and listed in the background picker instead of the layer list. Any other value is ignored\n(it does not group layers: groups are `{ title, elements: [...] }` objects).\nTo let only one of several layers be visible at a time, use `toggleGroup`.",
        "type": "String",
        "examples": [
          "[{visibility: true, group: \"background\", name: \"CSR:planet_brasil_2024\", priority: 5}]"
        ]
      },
      {
        "key": "maxExtent",
        "description": "Defines a geographical limit for a map rendering.",
        "type": "OpenLayers.Bounds"
      },
      {
        "key": "maxZoom",
        "description": "Defines the limit of the real max zoom of a given layer.\nWhen map zoom exceed this, the image is reused and stretched.",
        "type": "Numeric"
      },
      {
        "key": "maxscale",
        "description": "Defines the limit of the real max scale of a given layer. \nWhen the map scale value exceeds the defined one, the image is reused and stretched.",
        "type": "Numeric"
      },
      {
        "key": "metadataUrl",
        "description": "Defines the url to show custom layers metadata information.\nIf empty, the default value is obtained from layer metadata from the WMS definition.\n\nPS: The linked domain must have CORS headers enabled.",
        "type": "string|null"
      },
      {
        "key": "minscale",
        "description": "Defines the limit of the real min scale of a given layer. \nWhen the map scale reaches a value lower than the defined one, the image is reused and compressed.",
        "type": "Numeric"
      },
      {
        "key": "openGroup",
        "description": "Defines if the group starts opened, even without any visible layers.\nSet it true to start open, false otherwise.\nPS: If one layer is visible, the group will start open.\nIt works at both levels: on a `viewTitle` group it makes that group start expanded; on a layer it\nexpands every `viewTitle` ancestor of the layer (see `GroupProperties.openGroup`).",
        "type": "Boolean",
        "default": "false"
      },
      {
        "key": "operation",
        "description": "Defines how the pixels of each map of a `source: 'calculate'` layer are decoded into the values\nthat `expression()` receives in `layersVals`. It is a comma-separated list with one token per map\nin `name`, in the same order; an empty token keeps that map on the default decoding (for\nexample `'raw,,sum'` leaves the second map on the default). Tokens are case and whitespace\ninsensitive. The accepted tokens (see `RawMaps.operations` for the full description of each one) are:\n  - *(empty or unknown)*: `NORMAL`, the regular WMS image, each pixel is a legend colour/category.\n  - `raw`: the value of the most centred original cell of the region covered by the pixel.\n  - `rgba`: the RGBA bytes of the pixel packed as one integer.\n  - `sum`: area-weighted sum of the covered cells.\n  - `average`: area-weighted mean of the covered cells.\n  - `max` / `min`: largest / smallest cell at least partially covered.\n  - `integral`: summed-area table, the sum over a rectangle from its four corners.\n  - `areaintegral`: summed-area table of the covered areas.\n  - `area`: original map area inside the covered region.\n  - `cells`: weighted count of original cells inside the covered region.\nEvery token other than the default and `rgba` requires the map to be published as a Raw Map offering\nthat operation; otherwise the console logs `Error: Operation X is not defined for layer Y`.",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Population from raw maps',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Total population of each pixel',\n           // The same raw map is stacked twice, read with two different operations,\n           // and the states map keeps the default decoding (empty token).\n           name: 'CSR:pop_density_estimate_2015,CSR:estados,CSR:pop_density_estimate_2015',\n           operation: 'average,,area',\n           source: 'calculate',\n           visibility: true,\n           updateAutomatically: true,\n           descriptionHtml: '{{hoverpixel}}',\n           expression: function(layersVals, inputs) {\n              var density = layersVals[0]; // people per km2 (area-weighted mean)\n              var stateName = layersVals[1]; // legend value of the regular WMS map\n              var areaKm2 = layersVals[2]; // area of the original cells covered by this pixel\n              return density * areaKm2;\n           },\n        },\n     ],\n  },\n]"
        ],
        "default": "string.empty"
      },
      {
        "key": "shouldKeepRecord",
        "description": "Defines what the 'X' (remove) button of the Legend Window does (see `showRemoveBtn`). When\n`true` the layer record is kept: the layer is only hidden and removed from the listing, so it\ncan be listed again from the group menu at the top. When `false` the layer is removed from\nthe map for good.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'Removing a layer for good',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'The X button removes this map from the query',\n           name: 'CSR:rios_principais',\n           source: 'local',\n           visibility: true,\n           shouldKeepRecord: false,\n        },\n     ],\n  },\n]"
        ],
        "default": "true"
      },
      {
        "key": "toggleGroup",
        "description": "Places a layer in a group of layers with mutually exclusive visibility.\n\nAt most one layer of the same group will be visible, when one is shown, the others will be hidden.",
        "type": "String",
        "examples": [
          "[{name: \"CSR:estados\": toggleGroup:\"political_division\"},\n{name: \"CSR:paises\": toggleGroup:\"political_division\"}\n]"
        ]
      },
      {
        "key": "useLayerTooltip",
        "description": "If false, the layer tooltip is not shown. \nIf a number, the layer tooltip is shown for the specified layer. (Only supported for layers with `source: \"calculate\"`)\nOtherwise follow tooltip default behavior that varies from map source",
        "type": "Boolean|Number"
      },
      {
        "key": "verticalMenu",
        "description": "Uses the compact layout for the Legend Window of the layer: the secondary buttons (remove,\nmetadata, download, zoom to extents) and a vertical opacity slider move into a small options\nmenu opened by a gear button, instead of being drawn in the row with the horizontal opacity\nslider. Usually used together with `customLayerClass`.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'Compact Legend Window',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'The layer options are in a vertical menu',\n           name: 'CSR:geologia',\n           source: 'local',\n           visibility: true,\n           verticalMenu: true,\n           customLayerClass: 'my-compact-layer',\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      }
    ],
    "LayersFunctions": [
      {
        "key": "_information",
        "description": "Functions you write in a layer and the platform calls: expression, afterCalc and legendColor on calculated layers; beforeCalc, onInputsReady, onVisibilityChange and the functions map on calculated and file layers. Inside them this is the layer - except in expression, see Kinds of layer.",
        "name": "Layer callbacks: functions you write"
      },
      {
        "key": "afterCalc",
        "description": "This function is executed right after the 'expression()' calculations.\nThis function is only available for a Layer with a 'source' of type 'calculate' and that has defined the 'expression()' function.\n@param inputs {Array} The value of each input defined in the descriptionHtml. The order of the values is the same as the inputs (i.e. the first input in the descriptionHtml is inputs[0], the second is inputs[1] and so on).",
        "type": "function",
        "examples": [
          "[\n  {\n     title: 'Example of afterCalc function',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This afterCalc function will show a message at the bottom right of the screen',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           paramsButtonConfig: [\n              {\n                 type: 'query',\n                 pressed: true\n              },\n           ],\n           descriptionHtml:\n              '{{label|text=The afterCalc is run after every expression() execution}}'\n              +\n              '{{textfield|fieldLabel=Enter your name|id=textInput|labelStyle=text-align:center;}}',\n           // The expression function is mandatory to use the afterCalc function.\n           expression: function(layerVals, inputs) {\n              return undefined;\n           },\n           afterCalc: function(inputs) {\n              let inputValue = inputs[0];\n              ExtjsUtils.ALERTIFY.log('Hello ' + inputValue + '!');\n           },\n        },\n     ],\n  },\n]"
        ],
        "default": "null"
      },
      {
        "key": "beforeCalc",
        "description": "This function is executed before any calculation is made in the 'expression()' function.\nIt runs on calculated layers (`source: 'calculate'`), even when there is no 'expression()', and on file layers\n(`source: 'file'`), where it runs again whenever an input changes or a resource finishes loading (see `VectorLayer.generateNewLegend`).\n@param inputs {Array} The value of each input defined in the descriptionHtml. The order of the values is the same as the inputs (i.e. the first input in the descriptionHtml is inputs[0], the second is inputs[1] and so on).",
        "type": "function",
        "examples": [
          "[\n  {\n     title: 'Example of beforeCalc function',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This beforeCalc function will show a message at the bottom right of the screen',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           paramsButtonConfig: [\n              {\n                 type: 'query',\n                 pressed: true\n              },\n           ],\n           descriptionHtml:\n              '{{label|text=The beforeCalc function will be called after every user interaction before any calculation}}'\n              +\n              '{{textfield|fieldLabel=Enter your name|id=textInput|labelStyle=text-align:center;}}',\n           beforeCalc: function(inputs) {\n              let inputValue = inputs[0];\n              ExtjsUtils.ALERTIFY.log('Hello ' + inputValue + '!');\n           },\n        },\n     ],\n  },\n]"
        ],
        "default": "null"
      },
      {
        "key": "expression",
        "description": "This function is executed for every pixel in the map. It can be used to process the information of all maps in the Layer to create a new one.\nThis function is called regularly to update the map.\n@param layerVals {Array.<LayerValues>} Is an array that has the value associated with the current pixel for each map defined in the 'name' property.\nThe values order is the same as the one in ‘name’. For example, if in the 'name' property we have 'name: CSR:geologia,CSR:altimetria' the layerVals[0] has the value for the 'CSR:geologia' map and the layerVals[1] has the value for the 'CSR:altimetria'.\n@param inputs {Array} The value of each input defined in the descriptionHtml. The order of the values is the same as the inputs (i.e. the first input in the descriptionHtml is inputs[0], the second is inputs[1] and so on).",
        "type": "function",
        "examples": [
          "[\n  {\n     title: 'Example of expression function',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This Layer is the result of the calculation of two maps',\n           name: 'CSR:geologia,CSR:altimetria',\n           source: 'calculate',\n           legendTitle: 'All geologys above 800m',\n           visibility: true,\n           expression: function(layerVals, inputs) {\n              let geologyValue = layerVals[0];\n              let altitudeValue = layerVals[1];\n\n              // Hide all values lower than 800\n              if (altitudeValue < 800) {\n                 return undefined;\n              }\n\n              return geologyValue;\n           },\n        },\n     ],\n  },\n]"
        ],
        "default": "null",
        "returnDescription": "Value of each processed pixel."
      },
      {
        "key": "functions",
        "description": "Associate custom functions to handle events on layer callbacks such as button callbacks or any layer callbacks.\nThese functions are scoped to the Layer and can be referenced by name on layer widget callbacks.\nFunctions can also be accessed using `this.functions['<function_name>']`.",
        "type": "Object",
        "examples": [
          "[\n  {\n     title: 'Example of functions property',\n     color: '#666699',\n     elements: [\n        {\n           title: 'Click the button to see a message',\n           name: 'CSR:geologia',\n           group: 'Query',\n           source: 'calculate',\n           visibility: true,\n           paramsButtonConfig: [\n              { \n                 type:'query',\n                 pressed: true,\n              },\n           ],\n           descriptionHtml:\n              '{{button|id=test_button|text=Click me!|handler=handleTestButtonClick}}',\n           functions: {\n              handleTestButtonClick: function() {\n                 ExtjsUtils.ALERTIFY.log('Button clicked!');\n              },\n           },\n        },\n     ],\n  },\n]"
        ],
        "default": "null"
      },
      {
        "key": "legendColor",
        "description": "This function generates the color of each value/category of the calculated map generated by the 'expression()' function.\nThe return value is the legend color that will be applied to the category.\nPS: This function callback is called in layer context.\n@param color {Array.<Numeric>} Array with three numbers (0 to 255) that define the RGB color of the original legend.\n@param inputs {Array.<Object>} The value of each input defined in the descriptionHtml. The order of the values is the same as the inputs (i.e. the first input in the descriptionHtml is inputs[0], the second is inputs[1] and so on).\n@param lastValue {String} The last legend value that had its color calculated.\n@param currentValue {String} The actual legend value that it's being calculated.\n@param ruleIndex {Numeric} The index of the actual legend value that it’s being calculated. I.e. The first legend value is 0, the second is 1, and so on.",
        "type": "function",
        "examples": [
          "[\n  {\n     title: 'Example of legendColor',\n     color: '#666699',\n     elements: [\n        {\n           title: 'The legendColor function will generate a custom color for each category, based on its value',\n           name: 'CSR:altimetria',\n           source: 'calculate',\n           updateAutomatically: true,\n           visibility: true,\n           expression: function(layersVals, inputs) {\n              return layersVals[0];\n           },\n           // This create a custom colors palette for the legend of the result map from the 'expression' function\n           legendColor: function(color, inputs, lastValue, currentValue, ruleIndex) {\n              let red = 0;\n              let green = currentValue * 255 / 1830;\n              let blue = 0;\n              \n              return [red, green, blue];\n           },\n        },\n     ],\n  },\n]"
        ],
        "returnDescription": "The new color to the value in [R,G,B] format."
      },
      {
        "key": "onInputsReady",
        "description": "This function is called when all inputs have been loaded and are ready. Even from Layers that aren’t visible.\n@param inputs {Array} The value of each input defined in the descriptionHtml. The order of the values is the same as the inputs (i.e. the first input in the descriptionHtml is inputs[0], the second is inputs[1] and so on).",
        "type": "function",
        "examples": [
          "[\n  {\n     title: 'Example of onInputsReady function',\n     color: '#666699',\n     elements: [\n        {\n           title: 'This Layer shows the starting value of the text input field',\n           name: 'CSR:geologia',\n           group: 'Query',\n           source: 'calculate',\n           visibility: true,\n           paramsButtonConfig: [\n              { \n                 type:'query',\n                 pressed: true,\n              },\n           ],\n           descriptionHtml: \n              '{{textfield|id=textInput|fieldLabel=Message|value=I love maps!|labelStyle=text-align:center;}}',\n           onInputsReady: function(inputs) {\n              let textInputValue = inputs[0];\n              ExtjsUtils.ALERTIFY.log('The text input initial value is: ' + textInputValue);\n           },\n        },\n     ],\n  },\n]"
        ],
        "default": "null"
      },
      {
        "key": "onVisibilityChange",
        "description": "This function is called whenever a Layer changes its visibility.\nLike, when the user toggle the value in the visibility button at the top right in the Legend Window.\n@param visibility {Boolean} It’s true if the Layer is visible or false otherwise.\n@param inputs {Object} The value of each input defined in the descriptionHtml. The order of the values is the same as the inputs (i.e. the first input in the descriptionHtml is inputs[0], the second is inputs[1] and so on).",
        "type": "function",
        "examples": [
          "[\n  {\n     title: 'Example of callback when changing the visibility',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'The function onVisibilityChange will be called when the visibility of this layer changes.',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           paramsButtonConfig: [\n              {\n                 type:\"query\",\n                 pressed: true\n              },\n           ],\n           descriptionHtml:\n              '{{label|text=You can change the map visibility by clicking at the eye icon on the top of this window}}',\n           onVisibilityChange: function(state, inputs) {\n              if(state) {\n                 ExtjsUtils.ALERTIFY.log('The layer is now visible');\n              }\n              else {\n                 ExtjsUtils.ALERTIFY.log('The layer is now hidden');\n              }\n           },\n        },\n     ],\n  },\n]"
        ],
        "default": "null"
      }
    ],
    "paramsButtonConfigProperties": [
      {
        "key": "_information",
        "description": "paramsButtonConfig is a key of calculated, file and tile layers: an array with one object per button on the layer's row (query, legend, download, metadata...). These are the keys of each object; a bare object instead of an array is read as the query button.",
        "name": "Row buttons (paramsButtonConfig)"
      },
      {
        "key": "enableToggle",
        "description": "Makes the row button of a `paramsButtonConfig` entry that mirrors a widget (an entry with\n`associatedButtonID`) a toggle (`true`) or a plain push button (`false`).\nPS: Once the mirrored widget is found the platform sets it from that widget (a checkbox or a\ntoggle button makes it a toggle), so your value only counts while the widget does not exist.",
        "type": "Boolean",
        "examples": [
          "paramsButtonConfig: [{ type: 'associated', associatedButtonID: 'my_button', enableToggle: false }]"
        ],
        "default": "true"
      },
      {
        "key": "hideBottomButton",
        "description": "Hides the text buttons at the bottom of the query and legend sections of the Legend Window (\"Show\nquery\"/\"Hide query\" and \"Show legend\"/\"Hide legend\"). Set it to 'true' to hide them or 'false' to display\nthem. It only works inside a `paramsButtonConfig` entry of `type: 'query'` (or in the bare object form\nof `paramsButtonConfig`, which is treated as the query entry); a `hideBottomButton` written directly on\nthe layer or in a group's `defaultProperties` is ignored.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'Example of paramButtonConfig hideBottomButton property',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'The buttons at the bottom of the Legend Window are hidden',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           descriptionHtml:\n              '{{label|text=There are no buttons in here}}',\n           paramsButtonConfig: [\n              {\n                 type: 'query',\n                 hideBottomButton: false,\n              },\n           ],\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "hideButton",
        "description": "Define if the associated button should be hidden. Set it to 'true' to hide the button or 'false' to show it.\nThis property only applies to the type: 'query'",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'Example of paramButtonConfig hideButton property',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'The query button in hidden',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 hideButton: true,\n              },\n           ],\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "iconCls",
        "description": "Define a class that will be added to the html in the associated button.\n\nThis class can be one that has a image on it like:\n   - 'gxp-icon-togglevisibility' is the icon of the Show/Hide Map Button (The first icon on the Legend Window, from left to right)\n   - 'gxp-icon-removelayers' is the icon of the Remove Layer Button (The second icon on the Legend Window, from left to right)\n   - 'gxp-icon-parameters-expand' is the icon of the Show Query Button (The third icon on the Legend Window, from left to right)\n   - 'gxp-icon-legend-expand' is the icon of the Show Legend Button (The forth icon on the Legend Window, from left to right)\n   - 'gxp-icon-downloadmapbutton' is the icon of the Download Button (The fifth icon on the Legend Window, from left to right)",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Example of paramButtonConfig iconCls property',\n     color: '#A020F0',\n     elements: [\n        {\n           title: 'The query button is the download icon:',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           descriptionHtml:\n              '{{label|text=Here is the query region}}',\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 tooltip: 'This is actually the query button',\n                 // Here you can add any class you choose\n                 iconCls: 'gxp-icon-downloadmapbutton',\n              },\n           ],\n        },\n     ],\n  },\n]"
        ],
        "default": "string.empty"
      },
      {
        "key": "pressed",
        "description": "Defines if the button should start pressed or not. Set it to 'true' for it to start pressed or 'false' for start unpressed.\nFor example, the 'query' button type set as pressed will display the Query region (where  the descriptionHtml content is) by default instead of the Legend region.\nThis property applies only to the types: 'query',",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     title: 'Example of paramButtonConfig pressed property',\n     color: '#A020F0',\n     elements: [\n        {\n           title: 'The param button config set the query button to be pressed at the start',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           descriptionHtml:\n              '{{label|text=The region of the descriptionHtml will start opened}}',\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 pressed: true,\n              },\n           ],\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "toggleGroup",
        "description": "Defines a group for all the buttons that has the same toggleGroup name. From all the buttons on the same group, only one can be active at each time. Whenever another one is activated the previous one collapses.",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Example of paramButtonConfig toogleGroup',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Layer 1',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           descriptionHtml:\n              '{{label|text=This is the description of Layer 1. Only one can be open each time.}}',\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 toggleGroup: 'Group1',\n              },\n           ],\n        },\n        {\n           title: 'Layer 2',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           descriptionHtml:\n              '{{label|text=This is the description of Layer 2. Only one can be open each time.}}',\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 toggleGroup: 'Group1',\n              },\n           ],\n        },\n        {\n           title: 'Layer 3',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           descriptionHtml:\n              '{{label|text=This is the description of Layer 3. Only one can be open each time.}}',\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 toggleGroup: 'Group1',\n              },\n           ],\n        },\n     ],\n  },\n]"
        ],
        "default": "string.empty"
      },
      {
        "key": "tooltip",
        "description": "Defines the help tooltip text that is displayed when the mouse hover the associated button.",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Example of paramButtonConfig tooltip property',\n     color: '#A020F0',\n     elements: [\n        {\n           title: 'The query button is the third of the buttons at the right:',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           descriptionHtml:\n              '{{label|text=Hover the query button. That is the third button from left to right.}}',\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 pressed: true,\n              },\n           ],\n        },\n     ],\n  },\n]"
        ],
        "default": "string.empty"
      },
      {
        "key": "type",
        "description": "Define which button will be affected by the configurations. The type can be one of the following:\n\n  - 'query': The query button shows or hides the Query panel (where the descriptionHtml is drawn).\n\n  - 'download': The download button is the one with a downward arrow. This button allows the user to download the dataset. It also defines which Layer should be downloaded when more than one Layer is shown by its internal layer index.\n\n  - 'metadata': The metadata button is the one with a script icon. This button displays a popup with more information about the maps used in the actual Layer. You can make it show one of the inside composite Layer by his index.\n\n  - 'associated': Creates a custom button that is linked to another button defined in the descriptionHtml. When one is pressed the other is also pressed.\n\n`paramsButtonConfig` is normally an array of entries and each entry must declare its `type` (an entry\nwithout `type` is ignored). It may also be a single object instead of an array: it is then treated as\none entry of `type: 'query'` (unless the object declares another `type`).\nPS: The properties are aditional, so the common must always be present, and for each type his corresponding property must be added.",
        "type": "String",
        "examples": [
          "// Example on how to use the query type paramsButtonConfig\n[\n  {\n     title: 'Example of paramButtonConfig types',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This Layer has custom buttons on the Legend Window',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           startLegendOpen: true,\n           descriptionHtml:\n              '{{label|text=The third button is the Query type}}',\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 tooltip: 'This is the query button',\n                 pressed: true,\n                 handler: function handleClick(button, clickEvent)  {\n                    ExtjsUtils.ALERTIFY.log('You\\'ve clicked the query button');\n                 },\n              },\n           ],\n        },\n     ],\n  },\n]",
          "// Example on how to use the metadata type paramsButtonConfig \n[\n  {\n     title: 'Example of paramButtonConfig types',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This Layer has custom buttons on the Legend Window',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           startLegendOpen: true,\n           descriptionHtml:\n              '{{label|text=The third button is the metadata type}}',\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 pressed: true,\n              },\n              { \n                 type: 'metadata',\n                 tooltip: 'This is the metadata button',\n                 handler: function handleClick(button, clickEvent)  {\n                    ExtjsUtils.ALERTIFY.log('You\\'ve clicked the metadata button');\n                 },\n              },\n           ],\n        },\n     ],\n  },\n]",
          "// Example on how to use the download type paramsButtonConfig \n[\n  {\n     title: 'Example of paramButtonConfig types',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This Layer has custom buttons on the Legend Window',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           startLegendOpen: true,\n           descriptionHtml:\n              '{{label|text=The last  button is the download type}}',\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 pressed: true,\n              },\n              { \n                 type: 'download',\n                 tooltip: 'This is the download button',\n                 handler: function handleClick(button, clickEvent)  {\n                    ExtjsUtils.ALERTIFY.log('You\\'ve clicked the download button');\n                 },\n              },\n           ],\n        },\n     ],\n  },\n]",
          "// Example on how to use the associated type paramsButtonConfig\n[\n  {\n     title: 'Example of paramButtonConfig types',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'This Layer has custom buttons on the Legend Window',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           startLegendOpen: true,\n           descriptionHtml:\n              '{{label|text=The third button is the associated type}}'\n              +\n              '{{button|id=test_button|text=This is the custom associated button|enableToggle=true}}',\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 pressed: true,\n              },\n              { \n                 type: 'associated',\n                 tooltip: 'This is the custom button',\n                 associatedButtonID: 'test_button',\n                 // The handler only triggers for the Legend Window button (not the descriptionHtml associatedButton)\n                 handler: function handleClick(button, clickEvent)  {\n                    ExtjsUtils.ALERTIFY.log('You\\'ve clicked the custom button');\n                 },\n              },\n           ],\n        },\n     ],\n  },\n]"
        ],
        "default": "'query'"
      }
    ],
    "paramsButtonConfigFunctions": [
      {
        "key": "_information",
        "description": "Functions a row-button object can carry; the platform calls them when the button is pressed or toggled.",
        "name": "Row button callbacks"
      },
      {
        "key": "handler",
        "description": "Defines a function that will be called when the associated button is clicked.\n@param button {Object} is the button that was clicked by the mouse.\n@param clickEvent {MouseEvent} is the object that carries more information about the click event.",
        "type": "function",
        "examples": [
          "[\n  {\n     title: 'Example of callback when clicking in a Legend Window button',\n     color: '#A020F0',\n     elements: [\n        {\n           title: 'The handleClick function will be called when the query button is clicked',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 handler: function handleClick(button, clickEvent) {\n                    ExtjsUtils.ALERTIFY.log('You clicked the query button');\n                 },\n              },\n           ],\n        },\n     ],\n  },\n]"
        ]
      },
      {
        "key": "toggleHandler",
        "description": "This function is called whenever the associated button change its toggle state.\nPS: Using custom function, ignore the associatedButtonID \"It doesnt have to even exists\".\n@param button {Ext.Button} The button element that was clicked\n@param state {Boolean} The next state of the button, true means pressed",
        "type": "function",
        "examples": [
          "[\n  {\n     title: 'Example of paramButtonConfig toogleHandler',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Layer 1',\n           name: 'CSR:geologia',\n           source: 'calculate',\n           visibility: true,\n           paramsButtonConfig: [\n              { \n                 type: 'query',\n                 toggleHandler: function handleToggle(button, state) {\n                    let queryButtonState = state ? 'pressed' : 'unpressed';\n                    console.log(`Layer 1 query button is: ${queryButtonState}`);\n                 },\n              },\n           ],\n        },\n     ],\n  },\n]"
        ],
        "default": "null"
      }
    ]
  },
  "Configuring Layers": {
    "_information": {
      "title": "Kinds of layer: what each source adds",
      "description": "source chooses the kind of a layer: a published map (no source, \"local\", or the key of a server registered with addRemoteWMSServer), a calculated layer (\"calculate\"), a file layer (\"file\") or a tile layer (\"xyz\"). Each group below belongs to one kind: the keys only that kind reads, and the methods its layer object has at run time - what this is inside its functions."
    },
    "QGIS": [
      {
        "key": "_information",
        "description": "A published map's styles are made when it is published, from its QGIS style file; the automatic publication creates the same-looking style. A query picks one with styles.",
        "name": "Published maps: styles from QGIS"
      },
      {
        "key": "styles",
        "description": "Where a map's styles come from: they are not written in the query. A map is\npublished with one or more styles, and when it is published from QGIS the\nlayer's QGIS style is converted at publication time, so the classes, colours and\nlegend labels the map shows online are the ones set in QGIS. Each converted style\nbecomes a Mappia style identifier - the map name without its namespace, plus `_`\nand an index - and those identifiers are what a query selects with the `styles`\nproperty, what the layer's style chooser lists, and what the legend renders.\n\nSo changing how a map looks is a publication step, not a query change: restyle it\nin QGIS, publish again, and reference the style from the query.",
        "type": "String",
        "examples": [
          "{\n       ...,\n         name: 'CSR:estados',\n         styles: 'estados_0',\n       ...\n  }"
        ]
      }
    ],
    "RawMaps": [
      {
        "key": "_information",
        "completeAPI": "View the complete Raw Maps <a href='/api/#category_RawMaps'>API here</a>.",
        "description": "A raw map is published with each pixel's value instead of a colour. A calculated layer reads such a map with operation (raw, sum, average...) to get exact cell values; without operation it gets the value of the map's legend at each pixel. The colours are then applied in the browser.",
        "name": "Calculated layers: raw maps and operation tokens"
      },
      {
        "key": "operations",
        "description": "Enumeration of the operations a map of a `source: 'calculate'` layer can be decoded with. This is what\nthe `operation` layer key selects, one token per map in `name`; the tokens are the lower-case names\nbelow, matched case and whitespace insensitive. The values are integers; `getFromString(token)` and\n`getName(value)` convert between the two forms.\n  - NORMAL (0, an empty or unknown token): a regular WMS image, each cell is a legend colour/category and not a Raw Map.\n  - RAW (1, `raw`): each resulting cell has the value of the most centred original cell of the aggregated region.\n  - RGBA (2, `rgba`): each resulting cell is an integer packing the RGBA bytes of the pixel (input and output are integers).\n  - SUM (3, `sum`): each resulting cell is the weighted sum of the cells of the covered region, the weight being the covered fraction of each cell (VALUE * COVERED_PERCENTAGE).\n  - AVERAGE (4, `average`): each resulting cell is the weighted mean of the cells of the covered region.\n  - MAX (5, `max`): the greatest cell at least partially covered by the region.\n  - MIN (6, `min`): the smallest cell at least partially covered by the region.\n  - INTEGRAL (7, `integral`): summed-area table, so the sum inside any rectangle is obtained from its four corner cells only.\n  - AREAINTEGRAL (8, `areaintegral`): summed-area table of the covered areas (no further description in the source).\n  - AREA (9, `area`): each cell carries the original map area inside the aggregated region, so totals can be computed at any scale with full precision.\n  - CELLS (10, `cells`): weighted count of the original cells inside the region.\nEvery operation other than NORMAL and RGBA requires the map to be published as a Raw Map that offers it.",
        "type": "Object",
        "examples": [
          "LayerWorkerUtils.MapOperations.getFromString('sum'); // 3 (LayerWorkerUtils.MapOperations.SUM)\nLayerWorkerUtils.MapOperations.getName(3); // 'SUM'"
        ]
      }
    ],
    "LayerInternal": [
      {
        "key": "_information",
        "completeAPI": "View all layer internal helper functions <a href='/api/#category_LayerInternal'>API here</a>.",
        "description": "Methods of a calculated layer that your functions call on this - this.setCalculateLegend in beforeCalc, this.changeLayers to switch its maps, this.getInputs... They are not keys you write in the layer.",
        "name": "Calculated layers: methods (this.)",
        "writtenAs": "this.{key}"
      },
      {
        "key": "callFunction",
        "description": "Calls one of the layer's own `functions` by name with the layer as `this`. Extra\narguments are passed through to the function.\n@param name {String} Key of the layer's `functions` object.\n@param anyArguments {*} Arguments forwarded to the function.",
        "type": "function(name, anyArguments) : *",
        "examples": [
          "this.callFunction('getAssociatedLayer'); // or this.callFunction('getAssociatedLayer', 'param1', 2, [], {}, 'param5');"
        ],
        "returnDescription": "Whatever the function returns."
      },
      {
        "key": "changeLayers",
        "description": "Replaces an internal layer at a given index.\nPS: It's highly recommended to pass multiple configurations in an array, instead of calling this function multiple times in the same callback.\n@param newConfigs {Array|Object} One or more layers configurations with a property 'index' indicating the replacing layer.\n Each config object must contain at least the following properties:\n  [{name: 'MAP_FULL_NAME', \n   styles: 'MAP_STYLE',\n   index: 'MAP_INDEX_TO_CHANGE'}, ... ]\n@param force {Boolean} Force redraw even if no change is done when the layer is the same.",
        "type": "function(newConfigs, force)",
        "examples": [
          "{\n     name: \"CSR:rodovias,CSR:municipios\",\n     otherNames: \"CSR:roads\",\n     beforeCalc: function(layerVals, inputVals) {\n         this.changeLayers([{name: 'CSR:roads', styles: fMapName + \"_1\", index: 0}]);\n     }\n  }"
        ]
      },
      {
        "key": "generateNewLegend",
        "description": "Schedules a new legend: cancels the calculations in progress, runs `beforeCalc(inputs)` with the\ncurrent input values and, when every tile is loaded and the layer is not paused, recalculates the\nmap and its legend. It is the supported way to force the layer to refresh itself from outside a\nnormal calculation cycle (for example from `onVisibilityChange` or a `{{button}}` handler).",
        "type": "function()",
        "examples": [
          "onVisibilityChange: function(visible, inputs) {\n   if (visible && !this.isCalculationPaused()) {\n      this.generateNewLegend();\n   }\n}"
        ]
      },
      {
        "key": "getInputs",
        "description": "Returns the current value of every input tool declared in `descriptionHtml`, in declaration order\n(the same array `beforeCalc`, `expression` and `afterCalc` receive as `inputs`). A value can be a\nsimple one (number, string) or a complex object (a CSV table, a picked point, ...). Use it from\ncallbacks that do not receive `inputs`, such as a `{{button}}` handler.",
        "type": "function() : Array",
        "examples": [
          "functions: {\n   onClickButton: function() {\n      var inputs = this.getInputs();\n      ExtjsUtils.ALERTIFY.log('First input: ' + inputs[0]);\n   }\n}"
        ],
        "returnDescription": "The input values in the order the tools are declared."
      },
      {
        "key": "getInsideLayers",
        "description": "Returns the OpenLayers WMS layers that compose this layer, one per map in `name` and in the same\norder (a copy of the internal array, so changing it does not affect the layer).",
        "type": "function() : Array.<OpenLayers.Layer.WMS>",
        "examples": [
          "var firstMapName = this.getInsideLayers()[0].params.LAYERS;"
        ],
        "returnDescription": "The inside layers."
      },
      {
        "key": "getLayerDefinedFunctionsByName",
        "description": "Returns one of the functions declared in the layer's `functions` object by its name (the same\nfunctions that markup tools reference by name, e.g. `handler=onClick`). Composed-layer twin of\n`VectorLayer.getLayerDefinedFunctionsByName`; the returned function is not bound, so call it with\n`.call(this, ...)` from a layer callback to keep the layer as `this`.\n@param name {String} Key of the layer's `functions` object.",
        "type": "function(name) : function|undefined",
        "examples": [
          "{\n   name: 'CSR:geologia',\n   source: 'calculate',\n   functions: {\n      onClick: function(feature, inputs) { ExtjsUtils.ALERTIFY.log('clicked'); }\n   },\n   beforeCalc: function(inputs) {\n      this.getLayerDefinedFunctionsByName('onClick').call(this, null, inputs);\n   },\n}"
        ],
        "returnDescription": "The function, or undefined when there is no function with that name."
      },
      {
        "key": "getLegendEntries",
        "description": "Returns the entries of the legend currently applied to the calculated map, in legend order. Each\nentry has the `[R, G, B]` colour of the pixels, the `title` shown in the legend (the value itself when\nno title was defined) and whether it represents the null value of the map. Returns an empty array\nbefore the first legend is generated. Use it to build a custom legend (e.g. an HTML gradient) or to\nmap a colour back to its category.",
        "type": "function() : Array.<{color: Array.<Number>, title: String, isNull: Boolean}>",
        "examples": [
          "afterCalc: function(inputs) {\n   var html = this.getLegendEntries().map(function(entry) {\n      return '<span style=\"background: rgb(' + entry.color.join(',') + ')\">' + entry.title + '</span>';\n   }).join('');\n}"
        ],
        "returnDescription": "The legend entries."
      },
      {
        "key": "getVisibleLayerIndexes",
        "description": "Returns the indexes (positions in the `name` list) of the composing maps that are currently visible,\nin ascending order. Useful inside `beforeCalc`/`expression` when some of the inside maps were\ntoggled with `setInsideLayerVisibility()`.",
        "type": "function() : Array.<Number>",
        "examples": [
          "beforeCalc: function(inputs) {\n   var visible = this.getVisibleLayerIndexes(); // e.g. [0, 2]\n}"
        ],
        "returnDescription": "Indexes of the visible inside maps."
      },
      {
        "key": "isCalculationPaused",
        "description": "Returns calculation status.\nTrue if calculations are paused, false otherwise.",
        "type": "function() : Boolean",
        "returnDescription": "Calculation is paused"
      },
      {
        "key": "pauseAndStopCalculations",
        "description": "Cancels the calculations in progress and pauses the start of new ones (the \"generating legend\"\nindicator is shown while paused). Call it when something the expression depends on starts loading,\nand `resumeCalculations()` when it is done. The pause is cumulative: every call needs a matching\n`resumeCalculations()` before the layer calculates again (`isCalculationPaused()` tells the state).",
        "type": "function()",
        "examples": [
          "onInputsReady: function(inputs) {\n   var layer = this;\n   layer.pauseAndStopCalculations();\n   ExtjsUtils.REQUEST.get('https://maps.csr.ufmg.br/theme/app/data/example.json', function(resp) {\n      layer.myData = JSON.parse(resp.responseText);\n      layer.resumeCalculations(); // redraws with the loaded data\n   });\n}"
        ]
      },
      {
        "key": "resumeCalculations",
        "description": "Undoes `pauseAndStopCalculations()`. Call it whenever a resource finished loading; when the last\npending pause is released the layer is redrawn (which recalculates it).\n@param qnt {Number} How many pauses to release at once. `0` releases nothing and never redraws;\n omitted means 1.",
        "type": "function(qnt)",
        "examples": [
          "this.resumeCalculations();"
        ]
      },
      {
        "key": "setCalculateLegend",
        "description": "Defines the legend of the calculated map yourself, which also skips the automatic legend calculation\nfor this cycle. It is meant to be called inside `beforeCalc`, the moment right before a new legend\nwould be computed, but it can also be called from a tool callback (e.g. a `{{button}}` handler).\nWhen the given entries equal the current legend nothing is redrawn.\n@param legendEntries {Array.<{color: Array.<Number>, value: *, title: String}>} Legend entries sorted by `value` in ascending order (entries out of order are ignored). `color` is the `[R, G, B]` colour of the entry, `value` the highest value that maps to it, `title` the label shown in the legend.",
        "type": "function(legendEntries)",
        "examples": [
          "beforeCalc: function(inputs) {\n   this.setCalculateLegend([\n      {color: [0, 0, 255], value: 500, title: 'Up to 500 m'},\n      {color: [255, 255, 0], value: 1000, title: 'From 500 m to 1000 m'},\n      {color: [255, 0, 0], value: 3000, title: 'Above 1000 m'},\n   ]);\n}"
        ]
      },
      {
        "key": "setInsideLayerVisibility",
        "description": "Shows or hides the maps that compose the layer (the ones listed in `name`) without touching the layer\nitself. Pass `layerIndex` to change one map only, omit it to change all of them. The platform hides\nevery inside map when the layer has an `expression()` (only the calculated result is shown) and shows\nthem all otherwise; use this to reveal one of the source maps behind a calculated result.\n@param visibility {Boolean} True to show the map(s), false to hide them.\n@param layerIndex {Number} Index of the map in the `name` list; when omitted every inside map is changed.",
        "type": "function(visibility, layerIndex)",
        "examples": [
          "afterCalc: function(inputs) {\n   this.setInsideLayerVisibility(true, 0); // show the first source map under the calculated result\n}"
        ]
      },
      {
        "key": "setLayerOperation",
        "description": "Sets the decoding operation of one of the maps of the composed layer (the same tokens accepted by the\n`operation` layer key, e.g. `'raw'`, `'sum'`, `'average'`; `''` or `null` means the default decoding).\nThe platform calls it from the `operation` key at creation; when called at runtime the new operation\nis only used after the map legends are read again (for example when `changeLayers` replaces a map).\n@param layerIndex {Number} Index of the map in the `name` list (0 based).\n@param operation {String} Operation token, or `''`/`null` for the default decoding.",
        "type": "function(layerIndex, operation)",
        "examples": [
          "this.setLayerOperation(1, 'sum');"
        ]
      }
    ],
    "ValuesCalculation": [
      {
        "key": "_information",
        "description": "Inside expression, this is the calculation, which runs in a separate worker - not the layer: this.nullValue, this.isNumeric(value)... Page globals are not there. The same helpers are reachable as ValuesCalculation.prototype.&lt;method&gt;.",
        "name": "Calculated layers: this inside expression",
        "writtenAs": "this.{key}"
      },
      {
        "key": "calcTilesValues",
        "description": "Internal — the pixel loop of a `calculate` layer: for every pixel of the tile it reads\none value per inner map (colour looked up in the map's legend, or the raw cell value for\n`raw`/`sum`/... operations) and calls the layer's `expression(layersVals, inputs)` with\nthem. Pixels that are null in every map become `this.nullValue` without calling the\nexpression. The expression is invoked with `this` bound to this ValuesCalculation\ninstance, which is why `this.isNumeric(v)` works inside an expression\n(`ValuesCalculation.prototype.isNumeric(v)` also works). Tenant code never calls this\ndirectly; the layer worker does.\n@param imageDataTiles {Array.<Uint8ClampedArray>} One ImageData `data` array per inner map, for the same tile position (without the result tile).\n@param inputs {Array} The layer's input values, in the order defined for the layer (what `expression` receives as its second argument).",
        "type": "function(imageDataTiles, inputs) : Array.<(Number|String)>",
        "returnDescription": "One result per pixel, in the tile's pixel order."
      },
      {
        "key": "getColorFromInt",
        "description": "Unpacks the four RGBA bytes packed in a 32-bit integer: byte 0 (lowest) is red, byte 1\ngreen, byte 2 blue and byte 3 alpha. This is the packing the `rgba` map operation uses,\nwhere a pixel colour travels as one integer (the layer worker paints such results with\n`getNonNullColorFromInt`). Inside an `expression` function `this` is the\nValuesCalculation instance, so `this.getColorFromInt(v)` works there;\n`ValuesCalculation.prototype.getColorFromInt(v)` also works, anywhere.\n@param value {Number} The packed RGBA integer.",
        "type": "function(value) : Array.<Number>",
        "examples": [
          "ValuesCalculation.prototype.getColorFromInt(0xFF0080FF); // [255, 128, 0, 255]",
          "expression: function(layersVals, inputs) {\n    var rgba = this.getColorFromInt(layersVals[0]); // an inner map with operation: \"rgba\"\n    return rgba[3] === 0 ? this.nullValue : (rgba[0] + rgba[1] + rgba[2]) / 3; // grey level\n}"
        ],
        "returnDescription": "`[r, g, b, a]`, each component in 0..255."
      },
      {
        "key": "getNonNullColorFromInt",
        "description": "`getColorFromInt` with a null check: returns `this.nullColor` (`null`) when the packed\ninteger is one of the values listed in `nullValues`, and the unpacked `[r, g, b, a]`\notherwise. Handy when a map encodes \"no data\" as one or more specific colours. Inside\nan `expression` function `this` is the ValuesCalculation instance, so\n`this.getNonNullColorFromInt(v, nulls)` works there;\n`ValuesCalculation.prototype.getNonNullColorFromInt(v, nulls)` also works, anywhere.\n@param value {Number} The packed RGBA integer (see `getColorFromInt`).\n@param nullValues {Array.<Number>} Packed integers that should be treated as null.",
        "type": "function(value, nullValues) : Array.<Number>|null",
        "examples": [
          "expression: function(layersVals, inputs) {\n    var rgba = this.getNonNullColorFromInt(layersVals[0], [0, 0xFFFFFFFF]);\n    return rgba === null ? this.nullValue : rgba[0];\n}"
        ],
        "returnDescription": "`[r, g, b, a]`, or `null` when `value` is in `nullValues`."
      },
      {
        "key": "getValueFromLegend",
        "description": "Turns a legend label into the value that best represents it — the same conversion the\ncalculation applies to every map legend before the expression sees `layersVals`. A\nnumeric label (`\"12\"`, `\"1,5\"`, `\"1.000\"`) becomes that number (a single comma is read\nas the decimal separator, `\"1.000\"`/`\"1,000\"` as thousands), a range\n(`\"10 - 20\"`, `\"10 – 20\"`, `\"10 to 20\"`) becomes its midpoint, a leading comparison\noperator (`\"> 800\"`, `\"<= 5\"`, `\"&gt; 800\"`) is stripped first, and any other text\n(`\"Floresta\"`) is returned unchanged. An empty label returns `null`. Use it to interpret\nlegend titles you read with `ExtjsUtils.LAYER.getLayerLegend` the same way the layer does.\nInside an `expression` function `this` is the ValuesCalculation instance, so\n`this.getValueFromLegend(label)` works there; `ValuesCalculation.prototype.getValueFromLegend(label)`\nalso works, anywhere. To mark a pixel transparent, pass/return `this.nullValue`.\n@param legend {String} The legend title to interpret.",
        "type": "function(legend) : Number|String|null",
        "examples": [
          "ValuesCalculation.prototype.getValueFromLegend(\"10 - 20\");   // 15\nValuesCalculation.prototype.getValueFromLegend(\"> 800\");     // 800\nValuesCalculation.prototype.getValueFromLegend(\"1,5\");       // 1.5\nValuesCalculation.prototype.getValueFromLegend(\"Floresta\");  // \"Floresta\""
        ],
        "returnDescription": "The number the label represents, the label itself when it is not numeric, or `null` when it is empty."
      },
      {
        "key": "isNumeric",
        "description": "Tells whether a value represents a number: a Number, or a String that is a plain\ndecimal/exponential number (`\"12\"`, `\"-3.5\"`, `\"1e3\"`). Map values read from a legend\ncan be either a number or the legend's text (e.g. `\"Floresta\"`), so use this in an\n`expression` before doing arithmetic on `layersVals[i]`. Inside an `expression` function\n`this` is the ValuesCalculation instance, so `this.isNumeric(v)` works there;\n`ValuesCalculation.prototype.isNumeric(v)` also works, anywhere.\n@param value {String|Number} The value to test.",
        "type": "function(value) : Boolean",
        "examples": [
          "[{\n    name: \"CSR:cultivo_cafe_cafe\",\n    source: \"calculate\",\n    expression: function(layersVals, inputs) {\n        // layersVals[0] may be a legend label instead of a number\n        return this.isNumeric(layersVals[0]) ? layersVals[0] * inputs[0] : this.nullValue;\n    }\n}]"
        ],
        "returnDescription": "`true` when the value is numeric, `false` otherwise."
      },
      {
        "key": "nullValue",
        "description": "The value that stands for \"no data\" in a calculation: `NaN`. Return `this.nullValue`\nfrom an `expression` to leave the pixel transparent, and compare against it (with\n`isNaN`) when an inner map's value may be null. Inside an `expression` function `this`\nis the ValuesCalculation instance, so `this.nullValue` is the way to reach it there\n(`ValuesCalculation.prototype.nullValue` works anywhere).",
        "type": "Number",
        "examples": [
          "expression: function(layersVals, inputs) {\n    if (isNaN(layersVals[0]) || layersVals[0] < inputs[0]) {\n        return this.nullValue; // transparent pixel\n    }\n    return layersVals[0];\n}"
        ]
      },
      {
        "key": "simplifyLegend",
        "description": "Returns a shorter representation of a numeric value for display in a legend: values with\nan absolute value below 0.005 are written in exponential notation with 2 decimals,\nother values below 1 are kept to 3 significant digits and everything else is rounded\nto 2 decimals. Non-numeric values are returned unchanged. Useful inside `afterCalc` or\n`legendTitle` code that formats the legend entries of a calculated layer. Inside an\n`expression` function `this` is the ValuesCalculation instance, so `this.simplifyLegend(n)`\nworks there (and `ValuesCalculation.prototype.simplifyLegend(n)` works anywhere).\n@param n {Number|String} The value to simplify.",
        "type": "function(n) : Number|String",
        "examples": [
          "ValuesCalculation.prototype.simplifyLegend(0.001234); // \"1.23e-3\"\nValuesCalculation.prototype.simplifyLegend(0.4567);   // \"0.457\"\nValuesCalculation.prototype.simplifyLegend(1234.5678); // 1234.57"
        ],
        "returnDescription": "The simplified value (a string when exponential notation or `toPrecision` was used), or `n` itself when it is not numeric."
      }
    ],
    "JsonLegendReader": [
      {
        "key": "_information",
        "description": "Reads the JSON legends of the maps of a Composed layer, reachable as layer.jsonLegendReader. Prefer ExtjsUtils.LAYER.getLayerLegend for the common case.",
        "name": "Calculated layers: legend reader"
      },
      {
        "key": "getJsonLegend",
        "description": "Returns the loaded legend entries of a map: an array of `{color: [r, g, b], title}`\nobjects in legend order, or `undefined` while the legend has not been loaded (or\nfailed). This is the documented route to a map's legend — it accepts the layer\nobject itself and is what `ExtjsUtils.LAYER.getLayerLegend(composedLayer, index)`\nuses; the inner `jsonLegends.getJsonLegend(id)` needs the unique id instead.\n@param layer {OpenLayers.Layer|String} The layer object (an inner map of a Composed layer, typically), or its unique id.",
        "type": "function(layer) : Array.<Object>|undefined",
        "examples": [
          "// inside afterCalc / onInputsReady of a Composed layer (this === the layer)\nvar entries = this.jsonLegendReader.getJsonLegend(this.layers[0]) || [];\nentries.forEach(function(entry) {\n    console.log(entry.title, ExtjsUtils.HTML.rgbToColorStyle(entry.color));\n});"
        ],
        "returnDescription": "The legend entries `{color: Array<Number>, title: String}`, or `undefined` when not loaded."
      },
      {
        "key": "getLayerOperationCellType",
        "description": "Returns the cell type (`LayerWorkerUtils.CellTypes`: `INT32`, `UINT32`, `FLOAT32`)\nthe calculation must use to decode a map under a given operation, as declared by\nthe map's description metadata. Falls back to `INT32` when the map does not declare\nthe operation (see `hasOperation`). Mostly informational for query authors; the\ncalculation calls it itself.\n@param layer {OpenLayers.Layer|String} The layer object, or its unique id.\n@param operation {Number} The operation, one of `LayerWorkerUtils.MapOperations`.",
        "type": "function(layer, operation) : Number",
        "examples": [
          "var cellType = layer.jsonLegendReader.getLayerOperationCellType(layer.layers[0], LayerWorkerUtils.MapOperations.RAW);\nconsole.log(cellType === LayerWorkerUtils.CellTypes.FLOAT32);"
        ],
        "returnDescription": "The cell type, one of `LayerWorkerUtils.CellTypes`."
      },
      {
        "key": "getLayerUniqueId",
        "description": "Returns the key under which the reader stores a layer's legend: the string itself\nwhen given a string, the layer's `id` (assigned by `Ext.id`) when given a WMS layer\nobject, and an empty string for anything else (a layer without `params`). Use it to\nbuild the id the `jsonLegends` manager methods expect.\n@param layer {OpenLayers.Layer|String} The layer object, or an id already.",
        "type": "function(layer) : String",
        "examples": [
          "var id = layer.jsonLegendReader.getLayerUniqueId(layer.layers[0]);\nvar loaded = layer.jsonLegendReader.jsonLegends.wasLoaded(id);"
        ],
        "returnDescription": "The layer's unique id."
      },
      {
        "key": "getLegend",
        "description": "Looks up the legend title of a colour in a map's legend: the `title` of the entry\nwhose `[r, g, b]` equals the first three components of `color`. Returns an empty\nstring when the map has no legend loaded, the colour is not in it, or `color` has\nfewer than three components. This is what turns a pixel colour read from an inner map\ninto its class name (e.g. for a tooltip); for the Composed layer's own calculated\nlegend use `ExtjsUtils.LEGENDS.getColorTitle(layer, r, g, b)` instead.\n@param layer {OpenLayers.Layer|String} The layer object, or its unique id.\n@param color {Array.<Number>} The colour to look up, `[r, g, b]` or `[r, g, b, a]`.",
        "type": "function(layer, color) : String",
        "examples": [
          "var title = layer.jsonLegendReader.getLegend(layer.layers[0], [0, 128, 0]); // \"Floresta\""
        ],
        "returnDescription": "The legend title, or `\"\"` when there is none."
      },
      {
        "key": "hasOperation",
        "description": "Tells whether a map supports a given decoding operation (see `RawMaps.operations`):\nthe `normal` and `rgba` operations are always supported (they only need the WMS\nimage), the others (`raw`, `sum`, `average`, ...) only when the map's description\nmetadata declares a cell type for them. Useful to check, after the legends loaded,\nwhether an `operation` you plan to set on a layer will actually work.\n@param layerID {OpenLayers.Layer|String} The layer object, or its unique id.\n@param operationType {Number} The operation, one of `LayerWorkerUtils.MapOperations` (e.g. `LayerWorkerUtils.MapOperations.RAW`).",
        "type": "function(layerID, operationType) : Boolean",
        "examples": [
          "var supportsRaw = layer.jsonLegendReader.hasOperation(layer.layers[0], LayerWorkerUtils.MapOperations.RAW);"
        ],
        "returnDescription": "`true` when the map supports the operation."
      },
      {
        "key": "jsonLegends",
        "description": "The legend store of the reader, keyed by layer unique id (see `getLayerUniqueId`:\na layer's `id`, e.g. `layer.layers[0].id` for an inner map of a Composed layer).\nQueries reach it as `layer.jsonLegendReader.jsonLegends` and mostly call\n`getJsonLegend(id)`; prefer the outer `getJsonLegend(layer)` or\n`ExtjsUtils.LAYER.getLayerLegend(layer, index)` when you hold the layer object.\nIts four methods:\n`getJsonLegend(layerUniqueId)` → `Array<{color: [r, g, b], title: String}>|undefined` —\nthe loaded legend entries (`undefined` while not loaded or after `remove`);\n`setJsonLegend(layerUniqueId, legendArray)` — stores a legend array for that id;\n`wasLoaded(layerUniqueId)` → `Boolean` — whether a legend (even an empty one) is stored;\n`remove(layerUniqueId)` — forgets the stored legend, so it is fetched again next time.",
        "type": "Object",
        "examples": [
          "// colour of the legend entry whose title matches a value, for a Composed layer's first map\nfunction getLegendColor(composedLayer, title) {\n    var entries = composedLayer.jsonLegendReader.jsonLegends.getJsonLegend(composedLayer.layers[0].id) || [];\n    for (var i = 0; i < entries.length; i++) {\n        if (entries[i].title === title) return ExtjsUtils.HTML.rgbToColorStyle(entries[i].color);\n    }\n    return null;\n}"
        ]
      },
      {
        "key": "setLayerLegend",
        "description": "Stores the legend of a layer from its JSON content, replacing whatever the reader had\nfor it. The platform calls it when a legend request finishes; a query can call it to\ninject a legend for a map that has none (or to override the server's), e.g. before a\n`calculate` layer reads the legend values. A string is evaluated as JSON.\n@param layer {OpenLayers.Layer|String} The layer object, or its unique id (see `getLayerUniqueId`).\n@param legendContent {String|Array.<Object>} The legend: an array of `{color: [r, g, b], title: String}` entries, or that array as JSON text.",
        "type": "function(layer, legendContent)",
        "examples": [
          "layer.jsonLegendReader.setLayerLegend(layer.layers[0], [\n    {color: [0, 128, 0], title: \"Floresta\"},\n    {color: [255, 255, 0], title: \"Agricultura\"}\n]);"
        ]
      }
    ],
    "FileLayer": [
      {
        "key": "_information",
        "description": "source: \"file\": where the features come from - json written in the query (the default type), type with a url, or type \"load\" with your own loadData.",
        "name": "File layers: data"
      },
      {
        "key": "json",
        "description": "Inline data of a `source: 'file'` layer with `type: 'json'`. Either a GeoJSON object (FeatureCollection,\nFeature or a geometry; its projection is taken from `fromProj`, its `crs` or guessed from the\ncoordinates) or an array of plain objects, each converted to a feature with\n`convertJsonEntryToFeature` (see `coordinates`/`getVector`). A JSON string is also accepted.",
        "type": "Array.<Object>|Object",
        "examples": [
          "{\n   name: 'CSR:inline_points',\n   source: 'file',\n   type: 'json',\n   fromProj: 'EPSG:4326',\n   json: [\n      {name: 'Belo Horizonte', lon: -43.94, lat: -19.92},\n      {name: 'Brasilia', lon: -47.88, lat: -15.79},\n   ],\n}"
        ]
      },
      {
        "key": "loadData",
        "description": "Custom loader of a `source: 'file'` layer with `type: 'load'`: `function(inputs, config)` called with\nthe layer as `this`, receiving the current input values and the layer definition. Add the features\nyourself (`this.addFeatures`, `this.loadGeojson`, ...) and trigger `startLoadingLayer` before and\n`endLoadingLayer` after every asynchronous load, so the layer knows when the data is ready\n(`onInputsReady`/`onLoad` depend on it).",
        "type": "function",
        "examples": [
          "{\n   name: 'CSR:filtered_file',\n   source: 'file',\n   type: 'load',\n   url: 'https://maps.csr.ufmg.br/theme/app/data/example.geojson',\n   loadData: function(inputs, config) {\n      var layer = this;\n      layer.events.triggerEvent('startLoadingLayer');\n      ExtjsUtils.REQUEST.get(config.url, function(resp) {\n         var geojson = JSON.parse(resp.responseText);\n         geojson.features = geojson.features.filter(function(feat) {\n            return feat.properties.area > 100;\n         });\n         layer.loadGeojson(geojson);\n         layer.events.triggerEvent('endLoadingLayer');\n      }, function() {\n         layer.events.triggerEvent('endLoadingLayer');\n      }, false, layer);\n   },\n}"
        ]
      },
      {
        "key": "onVisibilityChange",
        "description": "Callback when layer visibility is changed.\n\nPS: Is not called for the initial visibility of the layer.",
        "type": "function",
        "examples": [
          "[{...\n onVisibilityChange: function(visibilityChangeEvent) {\n\n   alert(\"layer visibility changed\");\n }\n}]\n\nLayer scope and evt as parameter."
        ]
      },
      {
        "key": "type",
        "description": "Defines how a `source: 'file'` layer gets its features (case insensitive). One of:\n\n'load': you load the data yourself in the `loadData` callback (`function(inputs, config)`, called with the layer as `this`).\n   The callback must trigger `this.events.triggerEvent('startLoadingLayer')` before and `this.events.triggerEvent('endLoadingLayer')` after each load (the calls are cumulative).\n'csv': fetches the CSV file at `url`; each line becomes a feature through `convertJsonEntryToFeature` (see `coordinates`/`getVector`).\n'json': uses the inline `json` data: a GeoJSON object (FeatureCollection, Feature or geometry) or an array of plain objects converted with `convertJsonEntryToFeature`.\n'jsonurl': fetches an array of plain objects from `url` and converts each one with `convertJsonEntryToFeature`.\n'geojsonurl': fetches a GeoJSON file from `url` (projection detected from its `crs`, `fromProj` or the coordinates).\n'empty': creates no feature; the usual choice for layers that are drawn into or filled later with `loadGeojson`/`addFeatures`.\n\nWhen omitted the type is 'json'; an unknown value falls back to 'jsonurl'.",
        "type": "String",
        "examples": [
          "[\n  {\n     title: 'Vector layers',\n     color: '#FFA500',\n     elements: [\n        {\n           title: 'Municipalities from a GeoJSON file',\n           name: 'CSR:municipalities_file',\n           source: 'file',\n           type: 'geojsonurl',\n           url: 'https://maps.csr.ufmg.br/theme/app/data/example.geojson',\n           visibility: true,\n        },\n        {\n           title: 'Empty layer to draw on',\n           name: 'CSR:drawing_file',\n           source: 'file',\n           type: 'empty',\n           visibility: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "\"json\""
      },
      {
        "key": "url",
        "description": "URL of the data of a `source: 'file'` layer, used by the types `csv` (a CSV file), `jsonurl` (a JSON\narray of plain objects) and `geojsonurl` (a GeoJSON file). Relative URLs are resolved against the\nMappia page. Also available inside `loadData` as `config.url` for `type: 'load'`.",
        "type": "String",
        "examples": [
          "{\n   name: 'example_points',\n   source: 'file',\n   type: 'jsonurl',\n   // shipped with the platform: [{\"lon\":-50, \"lat\":-10, \"name\":\"name1\"}, ...]\n   url: '/theme/app/data/points_example.json',\n   coordinates: {x: 'lon', y: 'lat'},\n}"
        ]
      }
    ],
    "VectorLayer": [
      {
        "key": "_information",
        "completeAPI": "View complete VectorLayer documentation <a href='/api/#category_VectorLayer'>API here</a>.",
        "description": "Keys and methods of a file layer: styles, clustering, click and hover callbacks (onClick, onHover), drawing (drawable), and the methods your functions call on this.",
        "name": "File layers: style, events, drawing"
      },
      {
        "key": "activateDrawMode",
        "description": "Select polygon/line tool and enter edit mode when needed (split-button menu).\n@param mode {String} 'polygon' or 'line' — must be one of the layer's configured `drawModes`.",
        "type": "function(mode) : Boolean",
        "returnDescription": "true if the mode was activated (entering edit mode if not already active);\n  false if `mode` isn't in the layer's `drawModes`."
      },
      {
        "key": "applyFeatureChanges",
        "description": "Finish sketch or deselect feature (triggers featureEditEnd when applicable).",
        "type": "function() : Boolean",
        "returnDescription": "true if a sketch was finished or a feature was deselected; false if\n  there was no active modify control."
      },
      {
        "key": "applyGeomOp",
        "description": "Replace geometries on this **drawable VectorLayer** with id/attribute control via one options object.\nOnly available on vector layers from ``VectorFileSource`` (e.g. ``drawable: true``).\nImplementation helpers live in {@link DrawableSplit.applyGeomOp}.\n@param sourceGeoms {Array.<OpenLayers.Geometry>} Features selected by `feature.geometry` identity.\n@param options {Object|function} If a function, treated as ``{ operation: fn }``.\n@param options.operation {String|function} ``'difference'`` | ``'intersection'`` | ``'keep'`` or ``function(sourceGeom, sourceFeature) => Geometry[]``.\n@param options.with {OpenLayers.Geometry} Operand for difference / intersection.\n@param options.attributes {String|function} ``'keep'`` | ``'empty'`` or ``function(sourceAttrs, pieceIndex, pieceCount) => Object``.\n@param options.preserveId {Boolean} First piece of each source keeps OpenLayers id/fid.",
        "type": "function(sourceGeoms, options) : Object",
        "examples": [
          "// Cut overlaps (difference) — ``layer`` must be a drawable VectorLayer\n  layer.applyGeomOp(intersecting, {\n    operation: 'difference', with: editedUnion\n  });\n  // Clip to CAR (intersection)\n  layer.applyGeomOp(outsideGeoms, {\n    operation: 'intersection', with: carGeometry\n  });\n  // Custom pieces + empty attrs\n  layer.applyGeomOp(geoms, {\n    operation: function(g) { return [myTransform(g)]; },\n    attributes: 'empty'\n  });"
        ]
      },
      {
        "key": "callFunction",
        "description": "Calls one of the layer's own `functions` by name with the layer as `this`;\nfalls back to a method of the layer with that name. Extra arguments are\npassed through to the function.\n@param name {String} Key of the `functions` object (or a layer method name).\n@param anyArguments {*} Arguments forwarded to the function.",
        "type": "function(name, anyArguments) : *",
        "examples": [
          "{...\n  functions: {\n    FUNCTION_NAME: function(parameter1) {\n      var layer = this;\n      console.log(this, parameter1);\n    }\n  },\n  beforeCalc: function () {\n    this.callFunction('FUNCTION_NAME', 'parameter1');\n  }\n}"
        ],
        "returnDescription": "Whatever the function returns; undefined when no function was found."
      },
      {
        "key": "cancelDrawing",
        "description": "Cancel the in-progress sketch/edit without committing changes.",
        "type": "function() : Boolean",
        "returnDescription": "true if a sketch was cancelled; false if there was no active modify control."
      },
      {
        "key": "cluster",
        "description": "AnimatedCluster options. Truthy object enables clustering even when\n`clusterDistance` is omitted.\n\nZoom / map state is **not** passed into callbacks — read it yourself via\n`this.layer.map` (call scope is the strategy) or `ExtjsUtils.JS.getMap()`.",
        "type": "Object",
        "examples": [
          "cluster: {\n  distance: function() {\n    return this.layer.map.getZoom() > 14 ? 40 : 80;\n  },\n  clusterKey: function(feature) {\n    var zoom = this.layer.map.getZoom();\n    if (zoom <= 10) return feature.attributes.regiao;\n    if (zoom <= 14) return feature.attributes.bairro;\n    return null; // distance only\n  },\n  clusterGeometry: function(cluster) {\n    var key = cluster.attributes.clusterKey;\n    var zoom = this.layer.map.getZoom();\n    if (zoom <= 10 && REGION_GEOMS[key]) return REGION_GEOMS[key];\n    if (zoom <= 14 && BAIRRO_GEOMS[key]) return BAIRRO_GEOMS[key];\n    return this.defaultClusterGeometry(cluster); // hull / point\n  },\n  minClusterPolygonPx: 28\n}"
        ]
      },
      {
        "key": "clusterDistance",
        "description": "Defines the minimum relative distance (in pixels) to clusterize points.\nSet 0 to never clusterize, or a value greater than 0 to set the minimum distance.\nFor a membership key or dynamic distance, use {@link VectorLayer.cluster} instead\n(or together — `cluster` options win; `clusterDistance` fills default `distance`).\nPS: When filters are applied, you can use the property 'count' on 'VectorLayer.styleMap' property to check how many points exists in the current cluster.\n\n(Standalone doc comment: the value is read inside `strategies` below, which\ncarries the `cluster` block, so this one is named explicitly.)",
        "type": "Numeric"
      },
      {
        "key": "convertJsonEntryToFeature",
        "description": "Converts a JsonObject into a Layer Feature (that can be added to layer).\n@param curObj {JsonObject} Object with properties.\n@param optionalGetLonLat {function} Optional function to get position coordinates from JSON.\n  P.S.: Optional function to get X,Y values from Obj. (By default Longitude and Latitude use fields: lon and lat)\n  function optionalGetLonLat(curObj) returns OpenLayers.LonLat\n    curObj: Object with JSON properties.\n  This function receive the 'curObj' and expects to returns the object properties.",
        "type": "function(curObj, optionalGetLonLat) : OpenLayers.Feature.Vector",
        "returnDescription": "Returns the Feature from JsonObj."
      },
      {
        "key": "coordinates",
        "description": "Redefines the coordinate property names to {x, y}.\nPS: Used only if the 'VectorLayer.getVector' is not defined.",
        "type": "Object",
        "examples": [
          "coordinates: {\n              'x': 'lon',\n              'y': 'lat'\n          }"
        ]
      },
      {
        "key": "defaultStyle",
        "description": "Defines the default style that will be applied to the geometry. \nPS: Accepts 'context' and 'rules'.\n    Property 'context': allows definition of functions.\n    Property 'rules': allows definition of filters (only the geometries that fit into this rule will be displayed).\n\nLook at some examples at: http://dev.openlayers.org/examples/",
        "type": "Object",
        "examples": [
          "defaultStyle: {color: '${getColor}', context: {getColor: function(attr){return 'green';} }"
        ]
      },
      {
        "key": "deselectFeature",
        "description": "Deselect the currently selected/edited feature without discarding it.",
        "type": "function() : Boolean",
        "returnDescription": "true if a feature was deselected; false if there was no active modify\n  control or nothing was selected."
      },
      {
        "key": "drawable",
        "description": "Makes a vector layer editable: users can sketch, edit and delete polygon\n(optionally polygon+line) features, with configurable overlap resolution.\nSet to `true` for defaults, or an object to configure:\n  - `maxGeomCount` {Number} — maximum number of features allowed on the layer.\n  - `onFeaturesChange` {Function(evt)} — fires on any feature-set change\n    (add/remove/clear/load); `evt.type` identifies the cause.\n  - `onGeomLimitReached` {Function} — called when a draw attempt would exceed `maxGeomCount`.\n  - `onEditToggle` {Function(active, detail)} — sketch/edit mode turned on or off.\n    `detail`: `{ active, editingIntent, reason }`, `reason` is `'toggle'` (user/tenant)\n    or `'visibility'` (layer show/hide).\n  - `onDrawingStatusChange` {Function(detail)} — fine-grained sketch/edit state for UI.\n    `detail.status`: `'off'|'ready'|'drawing'|'drawing_line'|'feature_selected'|'vertex_drag'`,\n    plus `drawMode`, `canFinishDrawing`, `canApplyFeatureChanges`, `sketchPointCount`,\n    `selectedFeature`.\n  - `drawModes` {Array<String>} — `['polygon']` (default) or `['polygon','line']`,\n    the sketch tools available in edit mode. Alias: `drawingTypes`.\n  - `drawingHints` {String|Boolean} — `false`|`true`|`'toolbar'`|`'bar'`|`'both'`,\n    lightweight on-map edit guidance.\n  - `onOverlap` {String|Array<String>} — a single action key (applied without prompt)\n    or an ordered list of keys to prompt for when a new/edited feature overlaps\n    another. Defaults to merge/cut/keep ({@link DrawableSplit.OVERLAP_ACTIONS}).\n    Applied by ``layer.resolveOverlap``.\n  - `buttons` {Object} — layer-row toolbar overrides: `edit`, `clear`, `removeLast`, `loadFile`.\n\nExposed at runtime as `layer.drawableController` — see VectorLayer.activateDrawMode,\nVectorLayer.finishDrawing, VectorLayer.cancelDrawing, VectorLayer.deselectFeature,\nVectorLayer.applyFeatureChanges.",
        "type": "Boolean|Object",
        "examples": [
          "[{\n  name: \"CSR:file_draw\", source: \"file\", type: \"empty\",\n  drawable: {\n    maxGeomCount: 5,\n    drawModes: ['polygon', 'line'],\n    onOverlap: ['merge', 'cut', 'keep'],\n    onDrawingStatusChange: function(detail) { console.log(detail.status); },\n    onEditToggle: function(active, detail) { console.log(active, detail.reason); }\n  }\n}]"
        ]
      },
      {
        "key": "fillGeometryLayer",
        "description": "Loads (or reloads) the layer's features from a data-loading config, following\nits `type` strategy: `load` calls `config.loadData`, `csv`/`jsonurl`/\n`geojsonurl` fetch `config.url`, `json` uses the inline `config.json`, `empty`\nadds nothing. Unknown types fall back to `jsonurl`. This is what the platform\ncalls with the layer definition; call it yourself to swap the data at runtime.\n@param config {GeometryLayerConfig} Data-loading configuration.",
        "type": "function(config)",
        "examples": [
          "layer.fillGeometryLayer({\n    url: 'https://maps.csr.ufmg.br/theme/app/data/conab/limite_macroregioes.geojson',\n    type: 'load',\n    loadData: function(inputs, config) {\n        var layer = this;\n        function featFilterCb(feat){\n            return ['31', '35'].indexOf(feat.properties.geocodigo.toString().substring(0, 2)) !== -1;\n        }\n        layer.events.triggerEvent('startLoadingLayer');\n        ExtjsUtils.REQUEST.get(config.url, function(resp) {\n            var jsonObjects = JSON.parse(resp.responseText);\n            jsonObjects.features = jsonObjects.features.filter(featFilterCb);\n            layer.addFeatures(layer.convertGeojson2Features(jsonObjects));\n            layer.events.triggerEvent('endLoadingLayer');\n        }, function() {\n            layer.events.triggerEvent('endLoadingLayer');\n        }, false, layer);\n    }\n})"
        ]
      },
      {
        "key": "findFeatureById",
        "description": "Find a feature by the id returned from ``getFeatureId`` (fid, id, or index).\nPrefer this over OpenLayers ``getFeatureById`` when callers use getFeatureId ids.\n@param featureId {String|Number} Id as returned by `getFeatureId`.",
        "type": "function(featureId) : OpenLayers.Feature.Vector|null",
        "returnDescription": "The matching feature, or null."
      },
      {
        "key": "finishDrawing",
        "description": "Commit in-progress polygon sketch (same as double-click).",
        "type": "function() : Boolean",
        "returnDescription": "true if a sketch was committed; false if there was no active modify control."
      },
      {
        "key": "fromProj",
        "description": "Defines the projection of the JSON (accepts only EPSG:4326 and EPSG:900913).",
        "type": "String",
        "examples": [
          "EPSG:900913"
        ]
      },
      {
        "key": "generateNewLegend",
        "description": "Runs the layer's `beforeCalc(inputs)` again with the current input values. It is the supported way\nto force a vector layer to refresh whatever `beforeCalc` builds (styles, filters, charts) from\noutside a normal cycle, for example from `onVisibilityChange`; the platform calls it itself when a\nresource finishes loading. Guard it with `isCalculationPaused()` and `waitingRenderAndLoadAssync`\nso it does not run while data is still loading.",
        "type": "function()",
        "examples": [
          "onVisibilityChange: function(evt) {\n   if (this.getVisibility() && !this.isCalculationPaused() && this.waitingRenderAndLoadAssync == 0) {\n      this.generateNewLegend();\n   }\n}"
        ]
      },
      {
        "key": "getEditingFeatureId",
        "description": "Feature id currently selected for vertex edit (drawable modify control), if any.",
        "type": "function() : String|Number|null"
      },
      {
        "key": "getFeatureId",
        "description": "Stable-enough id for a feature on this layer: ``fid``, then OpenLayers ``id``,\notherwise the feature's index in ``layer.features`` (no synthetic attributes).\n@param feature {OpenLayers.Feature.Vector} Feature of this layer.",
        "type": "function(feature) : String|Number|null",
        "returnDescription": "Its `fid`, `id` or index; null when it is not on the layer."
      },
      {
        "key": "getInputs",
        "description": "Returns the current value of every input tool declared in `descriptionHtml`, in declaration order\n(the same array the layer callbacks receive as `inputs`). Use it from callbacks that do not\nreceive `inputs`, such as a `{{button}}` handler or `onFeatureChangeCallback`.",
        "type": "function() : Array",
        "examples": [
          "var firstInput = this.getInputs()[0];"
        ],
        "returnDescription": "The input values in the order the tools are declared."
      },
      {
        "key": "getLayerDefinedFunctionsByName",
        "description": "Gets the layer inner function.\n\nDefine the layer inner function at VectorLayer.\nEx: {\n  name: 'CSR:map_name',\n  function: {\n    nameFunctionExample: function(){alert('a');}\n  }\n}\n@param name {String} Function name",
        "type": "function(name) : function|Undefined"
      },
      {
        "key": "getVector",
        "description": "Defines the callback function to parse the layer data and get the geometry.\nCallback Function getVector function(attribute, config)\n@param attribute {Object} Object with attributes.\n@param config {Object} Layer config.",
        "type": "function",
        "returnDescription": "Return or the lat long point or the vector with the complex geometry."
      },
      {
        "key": "hoverSelectedStyle",
        "description": "Defines the style that will be applied to the geometry when the mouse hovers over a selected feature. Default hover selected style is the selected style   \nPS: Accepts 'context' and 'rules'.\n    Property 'context': allows definition of functions.\n    Property 'rules': allows definition of filters (only the geometries that fit into this rule will be displayed).\nPS2: Hover Select depends on both 'onHover' and 'onClick' properties to work.\n\nLook at some examples at: http://dev.openlayers.org/examples/",
        "type": "Object",
        "examples": [
          "[{color: '${getColor}', context: {getColor: function(attr){return 'green';} }]"
        ]
      },
      {
        "key": "hoverStyle",
        "description": "Defines the style that will be applied to the geometry when mouse is hovering. \nPS: Accepts 'context' and 'rules'.\n    Property 'context': allows definition of functions.\n    Property 'rules': allows definition of filters (only the geometries that fit into this rule will be displayed).\nPS2: Hover depends on 'onHover' property that allows to hovering.\n\nLook at some examples at: http://dev.openlayers.org/examples/",
        "type": "Object",
        "examples": [
          "[{color: '${getColor}', context: {getColor: function(attr){return 'green';} }]"
        ]
      },
      {
        "key": "llbbox",
        "description": "Bounds of the features currently on the layer, recomputed automatically after features are\nadded or removed (and extended while drawing). Despite the name it is expressed in the map\nprojection, not in lon/lat, so it can be passed straight to `map.zoomToExtent`. Read-only;\nundefined until the first feature is added.",
        "type": "OpenLayers.Bounds",
        "examples": [
          "onLoad: function(inputs) {\n   if (this.llbbox) {\n      app.mapPanel.map.zoomToExtent(this.llbbox);\n   }\n}"
        ]
      },
      {
        "key": "loadGeojson",
        "description": "Function to load geojson and draw on the layer.",
        "type": "function()"
      },
      {
        "key": "onAdded",
        "description": "Called when the layer is on the map with its features loaded. If the layer is still\nfetching (`jsonurl` / `geojsonurl` / `csv` / async `load`), waits for that load to\nfinish first so `this.features` / `this.getDataExtent()` are available (a failed load\nstill calls it). `this` is the layer.\n\nAddedEvent {\n    element: {DOM} 'DOM element of the layer',\n    layer: {OpenLayers.Layer.Vector} 'Javascript Object of the layer',\n    map: {Map} 'MapPanel where the layer was added.',\n    type: {String} 'Type of the event ( added )'\n}",
        "type": "function()",
        "examples": [
          "onAdded: function(evt) {\n    var bounds = this.getDataExtent();\n    if (bounds) ExtjsUtils.JS.getMap().zoomToExtent(bounds);\n}"
        ]
      },
      {
        "key": "onBeforeFeatureChangeCallback",
        "description": "Defines the callback function that is called before a layer feature is added, removed or edited.\nPS: If it returns false, the change operation is canceled.\n@param operationType {String} Receive the operation type: 'add' or 'remove' when adding or removing respectively.\n@param arrFeatures {Array} Array of features affected.",
        "type": "function",
        "examples": [
          "{\n     ...,\n     onBeforeFeatureChangeCallback: function(operationType, arrFeatures) {\n         \n     },\n     ...\n}"
        ]
      },
      {
        "key": "onClick",
        "description": "Defines the callback function to the click event on Layer.\n@param event {Object} The click event Object.\n@param source {VectorFileSource} Auxiliary functions to deal with Vector Layer.\n@param inputs {Array.<Object>} Array with all layer input values.",
        "type": "function",
        "examples": [
          "{\n     ...,\n     onClick: function (feature) {\n    console.log(feature.attributes);\n         console.log(\"Triggered click event!\");\n     },\n     ...\n}",
          "[{\n  name:\"CSR:FileGeojson\",\n  source: \"file\",\n  type: \"geojsonurl\",\n  url: \"/theme/app/data/fip_interativo/amazonia/amazonia_municipios.geojson\",\n  onClick: function(feature, layerSource, inputs, toggleStatus) {\n    console.log(arguments);\n  }\n}]"
        ]
      },
      {
        "key": "onClickCfg",
        "description": "Defines the callback function to the Click Select controller.\nThe additional parameters are listed in http://dev.openlayers.org/docs/files/OpenLayers/Control/SelectFeature-js.html.",
        "type": "function"
      },
      {
        "key": "onFeatureChangeCallback",
        "description": "Defines a function that is called after a layer feature is added, removed or edited.\n@param operationType {String} One of: ``'add'`` (features added in bulk, e.g. after\n  loading data), ``'add1'`` (a single feature added interactively), ``'remove'``\n  (features removed), ``'editend'`` (a drawable-layer sketch/edit was committed),\n  ``'editvertex'`` (a vertex was dragged/modified while editing).\n@param arrFeatures {Array} Array of features affected.\n@param detail {Object} Optional context. For ``editend``: ``{ drawMode: 'polygon'|'line' }``.",
        "type": "function",
        "examples": [
          "{\n     ...,\n     onFeatureChangeCallback: function (operationType, arrFeatures) {\n         \n     },\n     ...\n}"
        ]
      },
      {
        "key": "onHover",
        "description": "Defines the callback function on 'Vector Layer' hover.\n@param evt {Object} Features.\n@param state {Boolean} True when hover starts, false when it ends.\n@param controller {Object} The controller itself.\n@param inputs {Object} Layer widget values.",
        "type": "function",
        "examples": [
          "{\n     ...,\n     onHover: function (evt, state, controller, inputs) {\n         console.log(state ? \"Started\": \"Ended\");\n     },\n     ...\n}",
          "[{\n  name:\"CSR:FileGeojson\",\n  source: \"file\",\n  type: \"geojsonurl\",\n  url: \"/theme/app/data/fip_interativo/amazonia/amazonia_municipios.geojson\",\n  onHover: function(feature, state, controller, inputs) {\n    console.log(feature.attributes);\n    console.log(arguments);\n  }\n}]"
        ]
      },
      {
        "key": "onInputsReady",
        "description": "Called when all inputs are ready on layer.",
        "type": "function"
      },
      {
        "key": "onLoad",
        "description": "Defines the callback function to be called after the layer is loaded or added to a map.\nPS: You can use 'this' to access layer properties.\n@param widgetValues {Array} inputs for onLoad callback",
        "type": "function",
        "examples": [
          "{\n     ...,\n     onLoad: function (widgetValues) {\n         print(this.title) // prints the layer title\n     },\n     ...\n}"
        ]
      },
      {
        "key": "onSelectionToggle",
        "description": "Defines the callback function to the unselect feature.\n@param event {Object} The click event Object.\n@param source {VectorFileSource} Auxiliary functions to deal with Vector Layer.\n@param inputs {Array.<Object>} Array with all layer input values.",
        "type": "function",
        "examples": [
          "[{\n  name:\"CSR:FileGeojson\",\n  source: \"file\",\n  type: \"geojsonurl\",\n  url: \"/theme/app/data/fip_interativo/amazonia/amazonia_municipios.geojson\",\n  onSelectionToggle: function(feature, layerSource, inputs, toggleStatus) {\n    console.log(feature.attributes);\n    alert(toggleStatus);\n  }\n}]"
        ]
      },
      {
        "key": "popupCallback",
        "description": "Not implemented for file layers: accepted but never called (see `popupTemplate`). Use\n`onClick`, which receives the clicked feature.\n@param attributes {Array} attributes for popupCallback\n@param inputs {Array} inputs for popupCallback",
        "type": "function",
        "examples": [
          "{\n     ...,\n     popupCallback: function (attributes, inputs) {\n         \n     },\n     ...\n}"
        ]
      },
      {
        "key": "popupTemplate",
        "description": "Not implemented for file layers: the value is accepted and kept on the layer, but nothing\nreads it, so no popup ever opens (the template belongs to the gxp feed sources this layer\ndoes not extend). Show a feature's attributes from `onClick` instead - for example with\n`ExtjsUtils.ALERTIFY.log` or `ExtjsUtils.TooltipHelper.CreateTooltipOnPosition`.",
        "type": "String",
        "examples": [
          "onClick: function (feature) { ExtjsUtils.ALERTIFY.log(\"<b>\" + feature.attributes.name + \"</b>\"); }"
        ]
      },
      {
        "key": "resolveOverlap",
        "description": "Resolve overlaps between edited and existing polygons on this drawable VectorLayer.\nUses ``drawable.onOverlap``: a single key applies immediately; an array prompts\nvia ``ALERTIFY.confirmChoice`` (``merge`` / ``cut`` / ``keep``).\n@param editedGeoms {Array.<OpenLayers.Geometry>} Geometries just drawn or edited.\n@param intersectingGeoms {Array.<OpenLayers.Geometry>} Existing geometries they overlap.\n@param options {Object} Optional overrides.\n@param options.onOverlap {String|Array.<String>} Override controller ``onOverlap``.\n@param options.message {String} Prompt text.\n@param options.onComplete {function} After the chosen action (or no-op).",
        "type": "function(editedGeoms, intersectingGeoms, options)",
        "examples": [
          "drawableLayer.resolveOverlap(editedGeoms, intersectingGeoms, {\n    onComplete: function() { drawableLayer.callFunction('validateGeometries'); }\n  });"
        ]
      },
      {
        "key": "selectController",
        "description": "The selection control created for the layer when `onClick`, `onSelectionToggle` or `onHover` is\ndefined (undefined otherwise). It is the `OpenLayers.Control.CustomSelectFeature` instance, so you can\ncall its `select(feature)`, `unselect(feature)`, `unselectAll()`, `activate()`/`deactivate()` from\nlayer callbacks, e.g. to select a feature found with `findFeatureById`.",
        "type": "OpenLayers.Control.CustomSelectFeature",
        "examples": [
          "functions: {\n   selectFirst: function() {\n      if (this.selectController && this.features.length) {\n         this.selectController.unselectAll();\n         this.selectController.select(this.features[0]);\n      }\n   }\n}"
        ],
        "default": "undefined"
      },
      {
        "key": "selectStyle",
        "description": "Defines the default style that will be applied to the selected geometry. \nPS: Accepts 'context' and 'rules'.\n    Property 'context': allows definition of functions.\n    Property 'rules': allows definition of filters (only the geometries that fit into this rule will be displayed).\nPS2: Selected depends on 'onClick' property that allows to select.\n\nLook at some examples at: http://dev.openlayers.org/examples/",
        "type": "Object",
        "examples": [
          "[{color: '${getColor}', context: {getColor: function(attr){return 'green';} }]"
        ]
      },
      {
        "key": "setDrawing",
        "description": "Defines drawing features on layers.\nPS: Function is available to the VectorLayer.\n@param enabled {Boolean} True to enable drawing, False otherwise.\n@param callbackOnAdd {function} Callback when a new feature is drew.\n  P.S.: You can use 'this' to access layer properties.\n  P.S.2: The callback function receives 2 parameters:\n         - vectorLayer: Current layer.\n         - drawEvent: The info of the added polygon.",
        "type": "function(enabled, callbackOnAdd)",
        "examples": [
          "this.setDrawing(false, function (vectorLayer, drawEvent) {})"
        ]
      },
      {
        "key": "showLoadingModal",
        "description": "Shows the \"generating legend\" loading mask visibly over the page while the layer is paused (loading\nits data or another resource). By default the mask is an invisible overlay that only changes the\ncursor to \"wait\"; set it to `true` in the layer definition to make it visible. Only shown while the\nlayer is visible. The same key exists for `calculate` layers (`LayersProperties.showLoadingModal`).",
        "type": "Boolean",
        "examples": [
          "{\n   name: 'CSR:my_points',\n   source: 'file',\n   type: 'geojsonurl',\n   url: 'https://maps.csr.ufmg.br/theme/app/data/example.geojson',\n   showLoadingModal: true,\n}"
        ],
        "default": "false"
      },
      {
        "key": "styleMap",
        "description": "Customizes the layer visualization. \nPS: It is an advanced parameter, so it might be easier to use selectedStyle, defaultStyle and hoverStyle.\nPS2: The 'hover' style is applied on hover event.",
        "type": "Object|OpenLayers.StyleMap",
        "examples": [
          "StyleMap{[default, select, hover, selectedHover]: Style {rules:[Rule,], context: {getRadius: function() {return Math.random()}}}"
        ]
      },
      {
        "key": "waitingRenderAndLoadAssync",
        "description": "Number of asynchronous resources of the layer still loading (its own data for `csv`, `json`, `jsonurl`,\n`geojsonurl` and `load` types, plus `{{loadcsv}}`/`{{loadjson}}` inputs and any `startLoadingLayer`\nyou trigger yourself). It is increased on `startLoadingLayer`/`startLoadingResource` and decreased on\n`endLoadingLayer`/`endLoadingResource`; `onInputsReady` fires when it reaches 0. Read-only: use it as\na guard before refreshing the layer.",
        "type": "Number",
        "examples": [
          "if (this.waitingRenderAndLoadAssync == 0) { this.generateNewLegend(); }"
        ],
        "default": "0"
      }
    ],
    "CustomSelectFeature": [
      {
        "key": "_information",
        "description": "The select/hover control created for vector layers with onClick/onHover callbacks, reachable as layer.selectController. Extends OpenLayers.Control.SelectFeature.",
        "name": "File layers: click and hover control"
      },
      {
        "key": "click",
        "description": "Select on click. When `true`, clicking a feature selects it (`select`), clicking a\nselected one again unselects it when `toggle` is set, and clicking outside unselects\nall when `clickout` is set. The platform sets it for layers with `onClick` /\n`onSelectionToggle`. When `false`, `select`/`unselect` are no-ops.",
        "type": "Boolean",
        "default": "false"
      },
      {
        "key": "clickFeature",
        "description": "Internal — feature-handler callback run when a feature is clicked (only registered\nwhen `click` is `true`): toggles the feature off when it was selected and `toggle`\nis set, otherwise unselects the others (unless `multiple`) and selects it. Tenant\ncode normally does not call it; call `select`/`unselect` instead.\n@param feature {OpenLayers.Feature.Vector} The clicked feature.",
        "type": "function(feature)"
      },
      {
        "key": "clickoutFeature",
        "description": "Internal — feature-handler callback run when the user clicks outside a previously\nclicked (selected) feature: unselects everything when the `clickout` option is set.\nTenant code normally does not call it.\n@param feature {OpenLayers.Feature.Vector} The feature that was selected before the click.",
        "type": "function(feature)"
      },
      {
        "key": "defaultStyle",
        "description": "Name of the StyleMap render intent used to draw a feature that is neither hovered\nnor selected (`getFeatureCurrentStyle` returns it in that case).",
        "type": "String",
        "default": "\"default\""
      },
      {
        "key": "getFeatureCurrentStyle",
        "description": "Returns the name of the StyleMap render intent a feature should be drawn with\nright now, given whether it is selected (in `layer.selectedFeatures`) and whether\nit is hovered: `selectHoverStyle` when both, `selectedStyle` when only selected,\n`hoverStyle` when only hovered, `defaultStyle` otherwise. Useful when redrawing a\nfeature yourself (`layer.drawFeature(feature, style)`) after changing its attributes.\n@param feature {OpenLayers.Feature.Vector} The feature to inspect.\n@param hovered {Boolean} Whether the mouse is currently over the feature.",
        "type": "function(feature, hovered) : String",
        "examples": [
          "feature.attributes.value = newValue;\nlayer.drawFeature(feature, layer.selectController.getFeatureCurrentStyle(feature, false));"
        ],
        "returnDescription": "The render intent name, e.g. `\"select\"`."
      },
      {
        "key": "highlight",
        "description": "Highlights a feature as if the mouse were over it, without selecting it: redraws it\nwith `hoverStyle` (or `selectHoverStyle` when it is selected) and fires the\ncontrol's `beforefeaturehighlighted`/`featurehighlighted` events — which, on a file\nlayer, run the `onHover` callback with state `true`. Use it to mirror a hover coming\nfrom a list or chart outside the map; call `unhighlight` to revert.\n@param feature {OpenLayers.Feature.Vector} The feature to highlight.",
        "type": "function(feature)",
        "examples": [
          "// highlight a municipality when its row is hovered in a table\nlayer.selectController.highlight(layer.findFeatureById(rowId));"
        ]
      },
      {
        "key": "hover",
        "description": "Highlight on mouse over and unhighlight on mouse out (without selecting). The\nplatform sets it for layers with `onHover`; the `featurehighlighted` /\n`featureunhighlighted` events of the control fire the layer's `onHover` callback.",
        "type": "Boolean",
        "default": "false"
      },
      {
        "key": "hoverStyle",
        "description": "Name of the StyleMap render intent used to draw a hovered, unselected feature. It\nmatches the `VectorLayer.hoverStyle` entry of the layer's `styleMap`.",
        "type": "String",
        "default": "\"hover\""
      },
      {
        "key": "multiple",
        "description": "Allows more than one selected feature at a time: with `false` selecting a feature\nunselects the previous one. Pass it through `onClickCfg: {multiple: true}`.",
        "type": "Boolean",
        "default": "false"
      },
      {
        "key": "outFeature",
        "description": "Internal — feature-handler callback run when the mouse leaves a feature (only\nregistered when `hover` is `true`): unhighlights it, redrawing with `defaultStyle`\nor, if it is selected, `selectedStyle`; when another select control had highlighted\nthe feature before, that control's highlight is restored instead. Tenant code\nnormally does not call it; call `unhighlight` instead.\n@param feature {OpenLayers.Feature.Vector} The feature the mouse left.",
        "type": "function(feature)"
      },
      {
        "key": "overFeature",
        "description": "Internal — feature-handler callback run when the mouse enters a feature (only\nregistered when `hover` is `true`): highlights it, redrawing with `hoverStyle` or,\nif it is selected, `selectHoverStyle`. Tenant code normally does not call it; call\n`highlight` instead.\n@param feature {OpenLayers.Feature.Vector} The feature under the mouse.",
        "type": "function(feature)"
      },
      {
        "key": "select",
        "description": "Selects a feature programmatically, exactly as a click would: adds it to\n`layer.selectedFeatures`, redraws it with `selectedStyle` (or `selectHoverStyle`\nwhile hovered), fires the layer's `beforefeatureselected`/`featureselected` events\nand calls the `onSelect` option — so a file layer's `onClick`/`onSelectionToggle`\ncallback runs too. Does nothing when the control was created with `click: false`.\nUse it to select a feature from a list or a parent-page message; `unselectAll()`\n(inherited) clears the selection.\n@param feature {OpenLayers.Feature.Vector} The feature to select (must belong to the control's layer).",
        "type": "function(feature)",
        "examples": [
          "// select the feature whose attribute matches, from a tenant global called by a button\nvar layer = ExtjsUtils.LAYER.getLayerByName(\"CSR:municipios\");\nvar feature = layer.findFeatureById(featureId);\nif (feature) {\n    layer.selectController.unselectAll();\n    layer.selectController.select(feature);\n}"
        ]
      },
      {
        "key": "selectHoverStyle",
        "description": "Name of the StyleMap render intent used to draw a feature that is selected AND\nhovered. It matches the `VectorLayer.hoverSelectedStyle` entry of the layer's `styleMap`.",
        "type": "String",
        "default": "\"selectedHover\""
      },
      {
        "key": "selectedStyle",
        "description": "Name of the StyleMap render intent used to draw a selected feature that is not\nhovered. The platform passes `\"select\"` for file layers (the `VectorLayer.selectStyle`\nentry of the layer's `styleMap`); this is the stock default otherwise.",
        "type": "String",
        "default": "\"selected\""
      },
      {
        "key": "unhighlight",
        "description": "Removes the hover highlight of a feature: redraws it with the feature's own `style`\nif it has one, else the layer's `style`, else `defaultStyle` (or `selectedStyle`\nwhen it is selected), and fires the control's `featureunhighlighted` event — which,\non a file layer, runs the `onHover` callback with state `false`. When several\nselect controls highlighted the feature, the previous control's highlight is kept.\n@param feature {OpenLayers.Feature.Vector} The feature to unhighlight.",
        "type": "function(feature)",
        "examples": [
          "layer.selectController.unhighlight(layer.findFeatureById(rowId));"
        ]
      },
      {
        "key": "unselect",
        "description": "Unselects a feature programmatically: removes it from `layer.selectedFeatures`,\nredraws it with `defaultStyle` (or `hoverStyle` while hovered), fires the layer's\n`featureunselected` event and calls the `onUnselect` option — so a file layer's\n`onSelectionToggle` callback runs with its unselect status. Does nothing when the\ncontrol was created with `click: false`.\n@param feature {OpenLayers.Feature.Vector} The feature to unselect.",
        "type": "function(feature)",
        "examples": [
          "var selected = layer.selectedFeatures.slice();\nselected.forEach(function(feature) { layer.selectController.unselect(feature); });"
        ]
      }
    ],
    "CustomModifyFeature": [
      {
        "key": "_information",
        "description": "The vertex-editing control of drawable vector layers, reachable as layer.drawableController.modifyFeatureControl. Extends OpenLayers.Control.ModifyFeature.",
        "name": "File layers: vertex editing control"
      },
      {
        "key": "Drawing",
        "description": "Pure drawing/status constants and helpers of the modify control (no control instance\nneeded), reachable as `OpenLayers.Control.CustomModifyFeature.Drawing`. What a query\nuses from it is `DRAWING_STATUS`, the enum of the `detail.status` values that\n`drawable.onDrawingStatusChange(detail)` reports: `OFF: \"off\"` (not editing),\n`READY: \"ready\"` (editing active, nothing sketched or selected), `DRAWING: \"drawing\"`\n(a polygon sketch is in progress), `DRAWING_LINE: \"drawing_line\"` (a line sketch is in\nprogress), `FEATURE_SELECTED: \"feature_selected\"` (an existing feature is selected for\nediting) and `VERTEX_DRAG: \"vertex_drag\"` (a vertex of the selected feature is being\ndragged). It also holds `DRAW_MODES` (`POLYGON: \"polygon\"`, `LINE: \"line\"`, the\n`detail.drawMode` values), the default Portuguese `STATUS_LABELS` per status,\n`MIN_POLYGON_SKETCH_POINTS` (4) / `MIN_LINE_SKETCH_POINTS` (2) and\n`minSketchPointsForMode(drawMode)`. Compare against the constants instead of\nhard-coding the strings.",
        "type": "Object",
        "examples": [
          "drawable: {\n    onDrawingStatusChange: function(detail) {\n        var S = OpenLayers.Control.CustomModifyFeature.Drawing.DRAWING_STATUS;\n        finishButton.setDisabled(!(detail.status === S.DRAWING || detail.status === S.DRAWING_LINE) || !detail.canFinishDrawing);\n        applyButton.setDisabled(detail.status !== S.FEATURE_SELECTED);\n    }\n}"
        ]
      },
      {
        "key": "buildStatusDetail",
        "description": "Builds the current drawing/edit status of the control on demand — the same `detail`\nobject `drawable.onDrawingStatusChange(detail)` receives, so a host UI can read the\nstate at any moment (e.g. when it is first shown) instead of waiting for the next\nchange. Fields: `status` (one of `CustomModifyFeature.Drawing.DRAWING_STATUS`, never\n`\"off\"` here — the control only exists while editing), `drawMode`, `isDrawing`,\n`featureSelected`, `vertexDrag`, `selectedFeature` (the `OpenLayers.Feature.Vector`\nor `null`), `sketchPointCount`, `canFinishDrawing`, `canDeselectFeature`,\n`canApplyFeatureChanges` and `canCancelDrawing`. Reachable as\n`layer.drawableController.modifyFeatureControl.buildStatusDetail()`.",
        "type": "function() : Object",
        "examples": [
          "var detail = layer.drawableController.modifyFeatureControl.buildStatusDetail();\nif (detail.canApplyFeatureChanges) {\n    layer.drawableController.applyFeatureChanges();\n}"
        ],
        "returnDescription": "The status detail object described above."
      },
      {
        "key": "getActiveDrawMode",
        "description": "The geometry mode new sketches use on this control: `\"polygon\"` (default) or\n`\"line\"` (only when the layer's `drawable.drawModes` includes it), i.e. one of\n`CustomModifyFeature.Drawing.DRAW_MODES`. Reachable as\n`layer.drawableController.modifyFeatureControl.getActiveDrawMode()`; the same value\narrives as `detail.drawMode` in `onDrawingStatusChange`.",
        "type": "function() : String",
        "examples": [
          "var control = layer.drawableController.modifyFeatureControl;\nif (control.getActiveDrawMode() === OpenLayers.Control.CustomModifyFeature.Drawing.DRAW_MODES.LINE) {\n    console.log(\"next sketch cuts polygons along a line\");\n}"
        ],
        "returnDescription": "`\"polygon\"` or `\"line\"`."
      },
      {
        "key": "getSelectedFeatureId",
        "description": "Id of the feature currently selected for editing.",
        "type": "function() : String|Number|null"
      },
      {
        "key": "isFeatureSelected",
        "description": "Whether a feature of the layer is currently selected for editing on this control\n(its vertices are shown and draggable). Reachable as\n`layer.drawableController.modifyFeatureControl.isFeatureSelected()`; use\n`getSelectedFeatureId()` to know which one.",
        "type": "function() : Boolean",
        "examples": [
          "var control = layer.drawableController.modifyFeatureControl;\nif (control.isFeatureSelected()) {\n    console.log(\"editing feature \" + control.getSelectedFeatureId());\n}"
        ],
        "returnDescription": "`true` while a feature is selected for editing."
      }
    ],
    "XYZLayer": [
      {
        "key": "_information",
        "completeAPI": "View complete tile layer documentation <a href='/api/#category_XYZLayer'>API here</a>.",
        "description": "source: \"xyz\": tiles of a tile service, addressed by ${z}, ${x} and ${y} in url.",
        "name": "Tile layers (xyz)"
      },
      {
        "key": "name",
        "description": "Defines a map identifier. This name should be unique.",
        "type": "String",
        "examples": [
          "[\n ...\n     {\n        source: \"xyz\",\n        name:\"XYZ:map_name\",\n     }\n ...\n]"
        ]
      },
      {
        "key": "source",
        "description": "Adds a layer to store the source.\n@param layerCfg {LayerConfig} Layer XYZ configuration.\nLayerConfig {\n  url: url,\n  name: name,\n  isBaseLayer: isBaseLayer,\n  sphericalMercator: sphericalMercator\n}",
        "type": "Object"
      },
      {
        "key": "url",
        "description": "Defines the url where the map can be fetched from. \nUse the ${x} ${y} ${z} as placeholder for x, y and z coordinates.",
        "type": "String",
        "examples": [
          "\"https://tiles.planet.com/basemaps/v1/planet-tiles/planet_medres_visual_2021-09_mosaic/gmap/${z}/${x}/${y}.png?api_key=b24ae87a99624d2cbd8ed6aeb9703280\""
        ]
      }
    ]
  },
  "Groups": {
    "_information": {
      "title": "Groups: what you write in a group",
      "description": "A group holds layers, or other groups, in its elements. A top-level group with title is an entry of the top menu that lists its layers, each with a switch; a group with viewTitle is a heading in the layer panel. defaultProperties passes layer properties to every layer inside that does not set them itself.\nNot the same as the layer key group, of which only group: \"background\" does something (it makes the layer a basemap).\nTwo groups, each an entry of the top menu:",
      "example": "[\n   {\n      title: 'My first Group!',\n      color: '#A020F0',\n      elements: [\n         {\n            title: 'Layer 1 of group 1',\n            name: 'CSR:batimetria',\n            visibility: true,\n         },\n      ],\n   },\n   {\n      title: 'My second Group!!',\n      color: '#FFA900',\n      elements: [\n         {\n            title: 'Layer 1 of group 2',\n            name: 'CSR:altimetria',\n            visibility: true,\n         },\n      ],\n   },\n]"
    },
    "GroupProperties": [
      {
        "key": "_information",
        "description": "Keys of a group object, written next to its elements: title or viewTitle, color, openGroup, defaultProperties...",
        "name": "Group properties"
      },
      {
        "key": "color",
        "description": "Defines the Color of the group. This is the Color of the Title and some elements inside the sub menu on the top of the screen.",
        "type": "String",
        "examples": [
          "[\n   {\n      title: 'My orange group',\n      color: '#FFA500',\n   },\n]"
        ],
        "default": "string.empty"
      },
      {
        "key": "customLayerClass",
        "description": "Extra CSS class added to the node of a `viewTitle` group in the Legend Window, so the group title\nand its rows can be styled with `ExtjsUtils.CSS.defineClass` or a stylesheet. Only groups with a\n`viewTitle` create a node; on a layer the same key styles the layer row instead\n(`ConfigLayer.customLayerClass`).",
        "type": "String",
        "examples": [
          "[\n  {\n     viewTitle: 'Styled group',\n     viewColor: '#FFA500',\n     title: 'Group 1',\n     color: '#666699',\n     customLayerClass: 'highlighted-group',\n     elements: [\n        {\n           title: 'Layer 1',\n           name: 'CSR:estados',\n           source: 'local',\n           startListed: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "string.empty"
      },
      {
        "key": "defaultProperties",
        "description": "Specifies properties that apply universally to all layers within a group, including nested subgroups and their respective layers.\nPriority is determined by specificity: a property defined at the layer level takes precedence, followed by properties defined in the nearest group, and so forth.",
        "type": "Object",
        "examples": [
          "[\n  {\n     title: 'Example of defaultProperties',\n     color: '#FFA500',\n     defaultProperties: {\n        source: 'local',\n        visibility: true,\n        opacity: 0.7,\n     },\n     elements: [\n        {\n           title: 'Layer 1',\n           name: 'CSR:geologia',\n        },\n        {\n           title: 'Layer 2',\n           name: 'CSR:rios_principais',\n        },\n        {\n           title: 'Layer 3',\n           name: 'CSR:estados',\n           // Any internal redefinition will override the defaultProperties\n           visibility: false,\n        },\n     ],\n  },\n]"
        ],
        "default": "{}"
      },
      {
        "key": "elements",
        "description": "Defines the Layers that will be part of the Group. Each Layer can have multiple maps inside it.\nAll maps inside a Layer will be shown together when that Layer is enabled.\nBesides that, all information about those maps can be used to calculate a new one using custom functions that can be writen in JavaScript.",
        "type": "Array.<Layers>",
        "examples": [
          "[\n  {\n     title: 'A group with Layers!',\n     color: '#FFA500',\n     elements: [\n        // Define your Layers here\n     ],\n  },\n]"
        ],
        "default": "Array.empty",
        "see": [
          "To learn more about Layers and it’s properties, check their documentation at: [Layer Section](#group_Layers)."
        ]
      },
      {
        "key": "global",
        "description": "A second way to declare query globals: an object whose properties become temporary\nglobals, passed to `ExtjsUtils.QUERY.setQueryGlobalProperties` while the group is\ninterpreted (before its layers). The usual form is the\n`ExtjsUtils.QUERY.setQueryGlobalProperties({...}) && [...]` chain at the top of the\nquery; the production survey found no query using this key (zero users).",
        "type": "Object",
        "examples": [
          "[\n  {\n     title: 'Group with its own globals',\n     global: {\n        formatArea: function(value) { return ExtjsUtils.NUMBER.abbreviateNumber(value) + ' ha'; }\n     },\n     elements: [\n        { title: 'Layer 1', name: 'CSR:estados', source: 'local', visibility: true }\n     ]\n  }\n]"
        ]
      },
      {
        "key": "openGroup",
        "description": "Defines if the 'viewTitle' should start opening or collapse.\nSet it to 'true' for the 'viewTitle' start open or 'false' for it to start closed.\nThis property only applies to a group that has the 'viewTitle' property defined.\nIf the Group View has any visible Layers or any Layer has the 'openGroup' property set to 'true', the Group View will start open.\nIt works at both levels: on the `viewTitle` group it makes the group start expanded, on a layer it\nexpands every `viewTitle` ancestor of that layer.",
        "type": "Boolean",
        "examples": [
          "[\n  {\n     viewTitle: 'This Group View will start opened',\n     viewColor: '#FFFFFF',\n     title: 'Group 1',\n     color: '#666699',\n     openGroup: true,\n     elements: [\n        {\n           title: 'Layer 1',\n           name: 'CSR:estados',\n           source: 'calculate',\n           startListed: true,\n        },\n     ],\n  },\n  {\n     viewTitle: 'This Group View will start closed',\n     viewColor: '#FFFFFF',\n     title: 'Group 2',\n     color: '#666699',\n     elements: [\n        {\n           title: 'Layer 2',\n           name: 'CSR:rios_principais',\n           source: 'calculate',\n           startListed: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "false"
      },
      {
        "key": "title",
        "description": "Defines the Title of the group. The Title is shown at the sub menu on the top of the screen.",
        "type": "String",
        "examples": [
          "[\n   {\n     title: 'My new group!',\n   },\n]"
        ],
        "default": "string.empty"
      },
      {
        "key": "viewTitle",
        "description": "Define the Title of the View that will gather together the elements inside it (Groups or other Views).\nIf an external View has in its elements another definition of a 'viewTitle', subviews will be created, like in the second example.",
        "type": "String",
        "examples": [
          "// Exemple 1: View with a Layers inside it\n[\n  {\n     viewTitle: 'This View has a Group 3 Layers',\n     title: 'This is a Group with 3 Layers',\n     color: '#5BA300',\n     elements: [\n        {\n           title: 'Layer 1',\n           name: 'CSR:estados',\n           source: 'local',\n           opacity: 0.5,\n           visibility: true,\n        },\n        {\n           title: 'Layer 2',\n           name: 'CSR:rios_principais',\n           source: 'local',\n           opacity: 0.5,\n           visibility: true,\n        },\n        {\n           title: 'Layer 3',\n           name: 'CSR:geologia',\n           source: 'local',\n           opacity: 0.65,\n           visibility: true,\n        },\n     ],\n  },\n]",
          "// Exemple 2: View with others 'viewTitle' defined inside it\n[\n  {\n     // This is the definition of the View\n     viewTitle: 'This External View has 2 others Inner Views inside it, each with 1 Group that has 1 Layer',\n     title: 'This View has 2 Groups, each with 1 Layer',\n     color: '#0073E6',\n     elements: [\n        {\n           title: 'Group 1',\n           // This 'viewTitle' will create a division in the menu that shows when the mouse hovers the navigation bar option 'This View has 2 Groups, each with 1 Layer' \n           viewTitle: 'Inner View with Group 1',\n           color: '#E6308A',\n           elements: [\n              {\n                 title: 'Group 1 - Layer 1',\n                 name: 'CSR:altimetria',\n                 source: 'local',\n                 visibility: true,\n              },\n           ],\n        },\n        {\n           title: 'Group 2',\n           // This 'viewTitle' will create a division in the menu that shows when the mouse hovers the navigation bar option 'This View has 2 Groups, each with 1 Layer'\n           viewTitle: 'Inner View with Group 2',\n           color: '#B51963',\n           elements: [\n              {\n                 title: 'Group 2 - Layer 1',\n                 name: 'CSR:batimetria',\n                 source: 'local',\n                 visibility: true,\n              },\n           ],\n        },\n     ],\n  },\n]"
        ],
        "default": "string.empty"
      }
    ],
    "GroupFunctions": [
      {
        "key": "_information",
        "description": "Functions a viewTitle group can carry; the platform calls them when its heading in the layer panel is clicked or toggled. Inside them this is the heading's node.",
        "name": "Group callbacks (viewTitle groups)"
      },
      {
        "key": "onClickViewGroup",
        "description": "Called whenever the user clicks the title of a `viewTitle` group in the Legend Window. It belongs on\na group that has `viewTitle`: the handler is bound to that group's tree node, so `this` is the node\nand `view.expanded` tells whether the group is open after the click. When declared on a layer\ninstead of a group, it is attached to the layer's nearest `viewTitle` ancestor (once per layer\nthat declares it); on a flat layer with no `viewTitle` ancestor it is bound to the invisible\nroot node, i.e. it never fires.\n@param view {Ext.tree.TreeNode} The tree node of the group that was clicked (`view.expanded`, `view.text`, `view.childNodes`).\n@param clickEvent {Ext.EventObject} The click event, with details such as the mouse position.",
        "type": "function",
        "examples": [
          "[\n  {\n     viewTitle: 'Example View (Click me!)',\n     viewColor: '#FFA500',\n     title: 'Click the view to change its state',\n     color: '#666699',\n     openGroup: true,\n     onClickViewGroup: function handleClickViewGroup(view, clickEvent) {\n        let viewState = (view.expanded ? 'open' : 'closed');\n        ExtjsUtils.ALERTIFY.log('The ' + view.text + ' is ' + viewState );\n     },\n     elements: [\n        {\n           title: 'Layer 1',\n           name: 'CSR:estados',\n           source: 'local',\n           startListed: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "null"
      },
      {
        "key": "onToggleViewGroup",
        "description": "Called whenever a `viewTitle` group of the Legend Window is expanded or collapsed (by the user or by\ncode). It belongs on a group that has `viewTitle`: the handler is bound to that group's tree node,\nso `this` is the node and `view.expanded` is `true` after an expand and `false` after a collapse.\nWhen declared on a layer instead of a group, it is attached to the layer's nearest `viewTitle`\nancestor (once per layer that declares it); on a flat layer with no `viewTitle` ancestor it is\nbound to the invisible root node, i.e. it never fires. A typical use is hiding the layers of the\ncollapsed group and restoring them on expand.\n@param view {Ext.tree.TreeNode} The tree node of the group whose state changed (`view.expanded`, `view.text`, `view.childNodes`).",
        "type": "function",
        "examples": [
          "[\n  {\n     viewTitle: 'Example View (Click me!)',\n     viewColor: '#FFA500',\n     title: 'Click the view to change its state',\n     color: '#666699',\n     openGroup: true,\n     onToggleViewGroup: function handleToggleViewGroup(view) {\n        let viewState = (view.expanded ? 'open' : 'closed');\n        ExtjsUtils.ALERTIFY.log('The ' + view.text + ' is ' + viewState );\n     },\n     elements: [\n        {\n           title: 'Layer 1',\n           name: 'CSR:estados',\n           source: 'local',\n           startListed: true,\n        },\n     ],\n  },\n]"
        ],
        "default": "null"
      }
    ]
  },
  "Tools": {
    "_information": {
      "title": "Panel widgets",
      "description": "Controls written inside the descriptionHtml of a calculated, file or tile layer, as {{tag|param=value|param2=value}}: sliders, lists, buttons, windows. A widget with an id becomes an input of the layer's functions. Not the toolbar buttons of a map link (tools=, under Map links and embedding).\nThe markup rules shared by every widget come first; each widget also has a page with a live example under the Tools tab.\nExamples:",
      "example": "{\n    ...,\n    descriptionHtml: '{{toolIdentifier|paramName=paramValue}}',\n    ...\n}\n\n{\n    ...,\n    descriptionHtml: '{{button|id=btn_id|enableToggle=false}}' + '{{opacityslider}}',\n    ...\n}"
    },
    "MarkupSyntax": [
      {
        "key": "_information",
        "description": "Rules shared by every tool written inside descriptionHtml as {{tool|param=value|param2=value2}}: how parameters are parsed, the values they accept and the special parameters (getid, function, on_&lt;event&gt;, isnumeric, cls, key=false, nested a=b=c, escaped \\= ) available to all tools.\nFunction-valued parameters (handler, runOnClick, runOnHover, onSelect, ...) are resolved in this order: a key of the layer 'functions' object, then a global with that name (setQueryGlobalProperties), then the text itself evaluated as a function (a body, or a full 'function(){...}' expression).",
        "name": "Markup rules for every widget"
      },
      {
        "key": "cls",
        "description": "`cls=<class>` adds CSS classes to the created element. Unlike other keys, repeating it concatenates the\nvalues (with no separator — start the second one with a space, or list all classes in a single `cls`).",
        "type": "String",
        "examples": [
          "{{label|cls=slider_label bold|text=0%}}"
        ]
      },
      {
        "key": "defaultParsing",
        "description": "How a plain `param=value` is converted before reaching the tool: a value that looks like a number\n(`10`, `-4.5`, `1e3`) becomes a JavaScript number, an empty value (`param=`) becomes the empty string\n(falsy), and anything else stays a string — tools that need arrays or objects (`data`, `steps`, `values`,\n`filterLayers`, `backgroundColors`) parse the string themselves as JSON. `true`/`false` are NOT converted:\n`param=true` is the string `\"true\"` (truthy) and `param=false` is rewritten to an empty value (see\n`falseValue`). A parameter written without `=` (`|pressed|`) also gets the empty string, except for the\nspecial keys listed in this group (`isnumeric`). The same parameter written twice keeps the last value\n(`cls` and `on_<event>` accumulate instead).",
        "type": "String|Number",
        "examples": [
          "{{slider|id=s|minValue=0|maxValue=100|value=20|fieldLabel=Deforestation}}"
        ]
      },
      {
        "key": "escapedEquals",
        "description": "Write `\\=` to put a literal `=` inside a value; a bare `=` would start a nested key (see `nestedKeys`).\nNeeded above all in URLs with query strings. Inside a JavaScript string remember to double the backslash\n(`'\\\\='`). The value of `html=` is exempt: everything after its first `=` is kept as is.",
        "type": "String",
        "examples": [
          "{{loadcsv|id=csv|url=/wmtp/calc/areacategorical/?layers\\=CSR:estados&styles\\=1}}"
        ]
      },
      {
        "key": "falseValue",
        "description": "`param=false` is rewritten to `param=` (an empty, falsy value) before parsing, because the literal text\n`\"false\"` would otherwise be a truthy string. Use it to switch off a boolean parameter whose default is\ntrue (`unselect=false`, `notify=false`, `hideLabel=false`). Only a `false` at the very end of the parameter\nis rewritten.",
        "type": "Boolean",
        "examples": [
          "{{pickpoint|id=pick|unselect=false|notify=false}}"
        ]
      },
      {
        "key": "function",
        "description": "`function=<body>` stores a function whose body is the given text (`function () { <body> }`), evaluated\nwith no `try/catch` and no lookup in the layer `functions`. Use the nested form `param=function=<body>`\nto assign it to a parameter (a top-level `function=` only sets a `function` key); escape any `=` in the\nbody as `\\=`. Rarely needed: the callback parameters of the tools (`handler`, `runOnClick`, `onSelect`...)\nalready accept function names or inline text.",
        "type": "function",
        "examples": [
          "{{button|id=b|text=Log|handler=function=console.log('clicked')}}"
        ]
      },
      {
        "key": "getid",
        "description": "`getid=ID` gives access to an element created EARLIER in the same description (by any tag with that\n`id`). At the top level (`|getid=slider1|`) the element is stored in the tool config under `getid`;\nthe common use is to attach a listener to it with `getid=ID|getid=on_<event>=...` (see `on_event`), or the\nnested `param=getid=ID` to hand the element to a parameter (`scope=getid=btn`). Order matters: the\nreferenced tag must appear before the one using `getid`.",
        "type": "Object",
        "examples": [
          "{{slider|id=perc_slider|value=20}}{{label|id=perc_label|text=20%|getid=perc_slider|getid=on_change=Ext.getCmp('perc_label').setText(this.getValue() + '%')}}"
        ]
      },
      {
        "key": "html",
        "description": "`html=` is the one key whose value is taken verbatim up to the next `|`: `=` characters inside it are kept\nand no nesting happens. Used by `label` and `window`.",
        "type": "String",
        "examples": [
          "{{label|html=<a href=\"https://maps.csr.ufmg.br/?queryid=1\">open</a>}}"
        ]
      },
      {
        "key": "isnumeric",
        "description": "`|isnumeric|` (or `isnumeric=true`) installs a validator that only accepts numeric text — the field is\nmarked invalid with the localized \"must be a number\" message otherwise. Meant for `textfield`. Note that\nthe input value is still delivered as a string; convert it with `parseFloat`.",
        "type": "Boolean",
        "examples": [
          "{{textfield|id=soy_value|value=43|isnumeric|fieldLabel=Value (US$/Ton)}}"
        ]
      },
      {
        "key": "nestedKeys",
        "description": "`a=b=c` creates a nested object: the tool receives `{a: {b: c}}`. The innermost `key=value` is parsed with\nthe same rules as a top-level one, so the special keys work at any depth — `plugins=tip=...` builds a\nslider tip plugin, `scope=getid=ID` assigns an element created earlier, `listeners=on_change=...` is not\nneeded because `getid`/`on_<event>` already cover the common case.",
        "type": "Object",
        "examples": [
          "{{slider|id=s|plugins=tip={0}% of the area}}"
        ]
      },
      {
        "key": "on_event",
        "description": "`on_<event>=<code>` adds a listener for an Ext event (`change`, `select`, `afteredit`, `toggle`...) on an\nelement referenced with `getid` — the form is `getid=ID|getid=on_<event>=<code>`; it cannot be used on the\ntag's own element. `<code>` is either a full `function(...) {...}` or a statement body; it is evaluated\ndirectly (no lookup in the layer `functions` or in globals) and runs with `this` = the referenced\nelement and the event's own arguments. Several `on_` listeners may be attached to the same element.",
        "type": "function",
        "examples": [
          "{{textfield|id=price|isnumeric}}{{label|id=price_lbl|getid=price|getid=on_afteredit=Ext.getCmp('price_lbl').setText('US$ ' + this.getRawValue())}}"
        ]
      },
      {
        "key": "tip",
        "description": "`tip=<format>` builds an `Ext.slider.Tip` whose text is `String.format(format, thumbValue, value of thumb 0,\nvalue of thumb 1...)`, so `{0}` is the dragged thumb value. It must be assigned to a slider's `plugins`\nwith the nested form `plugins=tip=<format>`; a top-level `tip=` is stored in the config but not used by the\nslider.",
        "type": "Object",
        "examples": [
          "{{slider|id=perc_slider|plugins=tip={0}% of the area|increment=5}}"
        ]
      }
    ],
    "HTML": [
      {
        "key": "_information",
        "completeAPI": "",
        "description": "Any HTML is possible to be added to the layer description.",
        "name": "Free HTML"
      },
      {
        "key": "content",
        "description": "Free HTML inside a layer's `descriptionHtml`: everything that is not a `{{tag}}` is\npassed through to the panel untouched, so a description can carry headings, images,\nlinks, tables and the container elements a chart or a custom control needs. Widgets and\nHTML mix freely in the same string, and the HTML around a widget is rendered before the\nwidget is created, so an element declared here can already be referenced by an `id`.\n\nTwo things to keep in mind. Inside a `{{tag|...}}` the pipe separates parameters, so HTML\nthat must live in a parameter goes in `html=` (verbatim to the end of the value) or has\nits `=` escaped as `\\\\=`. And the description is written inside a JavaScript string in the\nquery, so quotes have to be escaped or alternated as usual.",
        "type": "String",
        "examples": [
          "descriptionHtml:\n  '<h3>Deforestation</h3>' +\n  '<p>Pick the minimum area to highlight.</p>' +\n  '{{slider|id=threshold|minValue=0|maxValue=100|value=20}}' +\n  '<div id=\"chart_area\" style=\"height:220px\"></div>'"
        ]
      }
    ],
    "AreaIntegral": [
      {
        "key": "_information",
        "completeAPI": "View the complete AreaIntegral <a href='/api/#category_AreaIntegral'>API here</a>.",
        "description": "Two clicks on the map define a rectangle; the tool sums the map values inside it using summed-area (integral) maps, so it only works with maps published with the 'integral' operation.\nCallback parameters (layersValues {Array[Number]}, inputs {Array}, boundingBox {Array[Number]} in EPSG:4326, pixel {x,y}, lastInfo {first, second})\nUsage: {{areaintegral|runOnClick=onAreaSummed}}",
        "name": "areaintegral · sum inside a rectangle (input)"
      },
      {
        "key": "getLayerValues",
        "description": "Reads the composed map at one screen position: one value per inner layer (decoded from the tile\ncolours through the legend, null cells read as 0) plus one extra entry with the layer `expression`\nresult for those values. The tool samples the four corners of the rectangle with it and combines\nthem as a summed-area table (`sum = minXY + maxXY - minXmaxY - maxXminY`), which is why the maps\nmust be published with the `integral` operation. Returns `null` when the layer is hidden or the\nposition is outside the map data.\n@param mousePoint {Object} Absolute screen position `{x, y}` to sample.\n@param layer {OpenLayers.Layer.Composed} The composed layer to read (normally the tool's own layer).",
        "type": "function(mousePoint, layer) : Array.<Number>|null",
        "returnDescription": "`[value of layer 0, ..., value of layer n-1, expression result]`, or `null`."
      },
      {
        "key": "iconCls",
        "description": "Defines the CSS class of the toggle switch icon; replace the default to restyle the switch.",
        "type": "String",
        "examples": [
          "|iconCls=my-toggle-icon|"
        ],
        "default": "cmn-toggle-icon"
      },
      {
        "key": "id",
        "description": "Defines the id of the tool: the key used in `inputs.id[ID]` and the id of the container component\n(`Ext.getCmp(id)`; the switch itself is `Ext.getCmp(id).items.get(0)`). Generated when omitted.",
        "type": "String",
        "examples": [
          "|id=integral_tool|"
        ]
      },
      {
        "key": "labelBefore",
        "description": "Set true to render the `text` label before (left of) the toggle switch instead of after it.",
        "type": "Boolean",
        "examples": [
          "|labelBefore=true|"
        ],
        "default": "false"
      },
      {
        "key": "notify",
        "description": "Set false to suppress the notification shown when the tool is activated.",
        "type": "Boolean",
        "examples": [
          "|notify=false|"
        ],
        "default": "true"
      },
      {
        "key": "runOnClick",
        "description": "Defines the callback run after the second click closes the rectangle, with the sums computed. Called\nas `runOnClick(layersValues, inputs, boundingBox, pixel, lastInfo)` with `this` = the layer:\n`layersValues` has one summed value per inner layer (see `getLayerValues`), `inputs` is\n`layer.getInputs()`, `boundingBox` is `[minLon, minLat, maxLon, maxLat]` in EPSG:4326, `pixel` the\n`{x, y}` of the second click and `lastInfo` the `{first, second}` `{lon, lat}` of the two clicks.\nThe name is resolved as a key of the layer `functions` object, then as a global function, then as\ninline function text. The tool deactivates itself after the callback.",
        "type": "function",
        "examples": [
          "|runOnClick=onAreaSummed|",
          "functions: {\n    onAreaSummed: function(layersValues, inputs, boundingBox, pixel, lastInfo) {\n        ExtjsUtils.ALERTIFY.log('Sum inside ' + boundingBox.join(', ') + ': ' + layersValues[0]);\n    }\n}"
        ]
      },
      {
        "key": "text",
        "description": "Defines the text shown next to the toggle switch.",
        "type": "String",
        "examples": [
          "|text=Sum a rectangle|"
        ]
      },
      {
        "key": "unselect",
        "description": "Only changes the activation message (`true`: one selection expected, `false`: several); the tool\nalways deactivates itself after the second click.",
        "type": "Boolean",
        "examples": [
          "|unselect=false|"
        ],
        "default": "true"
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[ID]`: the `layersValues` array of the tool — one summed value per inner\nlayer of the composed map for the last rectangle (empty before the first one). The array is updated in\nplace after the second click, so `beforeCalc`/`expression` always read the latest sums. The input is\nregistered on the `forceupdatelayer` event of the switch, which the tool does not fire by itself:\nreact inside `runOnClick`, or call `Ext.getCmp(id).items.get(0).forceUpdateLayer()` there to\nrecalculate the layer with the new sums.",
        "type": "Array.<Number>",
        "examples": [
          "{{areaintegral|id=integral_tool|runOnClick=onAreaSummed}}",
          "functions: {\n    onAreaSummed: function(layersValues, inputs) {\n        Ext.getCmp('integral_tool').items.get(0).forceUpdateLayer(); // beforeCalc will see inputs.id['integral_tool']\n    }\n}"
        ]
      }
    ],
    "Button": [
      {
        "key": "_information",
        "completeAPI": "This button is created from <a href='https://docs.sencha.com/extjs/3.4.0/#!/api/Ext.Button' target='_blank'>Button.Configs</a>.\nOnly some properties are listed in <a href='/api/#category_Button'>API here</a>.",
        "description": "Create a simple button to user interact with the map.",
        "name": "button · push or toggle button"
      },
      {
        "key": "enableToggle",
        "description": "Defines the button type as toggle.\nSet true to use as toggle, false otherwise.\nPS: When its true the callback is 'toggleHandler', otherwise the callback is 'handler'.",
        "type": "Boolean",
        "examples": [
          "|enableToggle=true|"
        ],
        "default": "false"
      },
      {
        "key": "fieldLabel",
        "description": "Defines the button label.",
        "type": "String",
        "examples": [
          "|fieldLabel=A button|"
        ]
      },
      {
        "key": "handler",
        "description": "Defines the callback function on button click event. This should be used\nwhen the enableToggle property is false. `this` inside the callback is the layer.\n\nThe value is resolved in this order: (1) a key of the layer `functions` object with that name;\n(2) a global function with that name (e.g. defined with `setQueryGlobalProperties`); (3) otherwise the\ntext itself is evaluated as a function — either a full `function(){...}` expression or a plain\nstatement body. With `enableToggle=true` and no `toggleHandler`, `handler` is used as the toggle handler.\n@param button {Ext.Button} The button element that was clicked.\n@param clickEvent {EventObject} An event object carrying information about the click event.",
        "type": "function",
        "examples": [
          "|handler=onExportClick|",
          "|handler = function (button, clickEvent){\n console.log(button, clickEvent);\n}|"
        ],
        "default": "undefined"
      },
      {
        "key": "hidden",
        "description": "Set true to create the button hidden (Ext `hidden` config); show it later with `Ext.getCmp(id).show()`.\nThis is one example of the pass-through: every other `Ext.Button` config (`iconCls`, `tooltip`, `cls`,\n`width`, `disabled`, `scale`...) written in the markup is handed to the button unchanged.",
        "type": "Boolean",
        "examples": [
          "|hidden=true|"
        ],
        "default": "false"
      },
      {
        "key": "id",
        "description": "Defines the id to identify the object.",
        "type": "String",
        "examples": [
          "|id=exemple_button|"
        ]
      },
      {
        "key": "pressed",
        "description": "Defines the button initial state. \nSet it true to start pressed (only if enableToggle = true), false otherwise.",
        "type": "Boolean",
        "examples": [
          "|pressed=true|"
        ],
        "default": "false"
      },
      {
        "key": "text",
        "description": "Defines the button text.",
        "type": "String",
        "examples": [
          "|text=Click On Me|"
        ]
      },
      {
        "key": "toggle",
        "description": "Alias of `toggleHandler`: a function given as `toggle=` is moved to `toggleHandler` (unless one is\nalready defined), so the Ext `toggle()` method of the button is never overwritten. Prefer\n`toggleHandler`.",
        "type": "function",
        "examples": [
          "|enableToggle=true|toggle=onToggleDetails|"
        ]
      },
      {
        "key": "toggleHandler",
        "description": "Defines the callback function on button toggle event. This should be used\nwhen the enableToggle property is true. `this` inside the callback is the layer.\n\nThe value is resolved in this order: (1) a key of the layer `functions` object with that name;\n(2) a global function with that name (e.g. defined with `setQueryGlobalProperties`); (3) otherwise the\ntext itself is evaluated as a function — either a full `function(){...}` expression or a plain\nstatement body.\n@param button {Ext.Button} The button element that was clicked.\n@param state {Boolean} The next state of the button, true means pressed.",
        "type": "function",
        "examples": [
          "|enableToggle=true|toggleHandler=onToggleDetails|",
          "|toggleHandler = function (button, pressed){\n console.log(button, pressed);\n}|"
        ],
        "default": "undefined"
      }
    ],
    "Checkbox": [
      {
        "key": "_information",
        "completeAPI": "This tool is created from <a href='https://docs.sencha.com/extjs/3.4.0/#!/api/Ext.form.Checkbox' target='_blank'>Ext.form.Checkbox</a>.\nOnly customized properties are <a href='/api/#category_Checkbox'>listed here</a>.",
        "description": "A checkbox that calls a function of the layer when toggled. It is not registered as an input: read its state inside the handler.\nUsage: {{checkbox|text=Show details|handler=onToggleDetails}}",
        "name": "checkbox · on/off, calls a function"
      },
      {
        "key": "checked",
        "description": "Set true to start checked. `handler` is not run for the initial state.",
        "type": "Boolean",
        "examples": [
          "|checked=true|"
        ],
        "default": "false"
      },
      {
        "key": "fieldLabel",
        "description": "Defines a label at the left of the whole field (Ext `fieldLabel`), in addition to the `text` shown\nnext to the switch. `hideLabel=true` removes it and its reserved space.",
        "type": "String",
        "examples": [
          "|fieldLabel=Options|"
        ]
      },
      {
        "key": "forceUpdateLayer",
        "description": "Fires the `forceupdatelayer` event of the widget. For the map-interaction tools registered as inputs\n(summedarea, areaintegral) this is the event the layer listens to, so calling it marks the input as\nchanged and recalculates the layer with the values currently stored in `inputs.id[ID]`.",
        "type": "function()",
        "examples": [
          "Ext.getCmp('sum_tool').items.get(0).forceUpdateLayer();"
        ]
      },
      {
        "key": "handler",
        "description": "Defines the callback run whenever the checkbox is checked or unchecked (by the user or by `toggle()`).\nCalled as `handler(checkbox, checked)` with `this` = the layer. The value is resolved in this order:\na key of the layer `functions` object, then a global function with that name, then the text itself\nevaluated as a function (a full `function(){...}` or a statement body). The checkbox is not a layer\ninput: read `checked` here and act (e.g. `setValues` on an InputManager, `changeLayers`...). It is\nalso called once with `checked=false` when the widget is removed together with its layer.",
        "type": "function",
        "examples": [
          "|handler=onToggleDetails|",
          "functions: {\n    onToggleDetails: function(checkbox, checked) {\n        this.getInputs().id['state'].setValues({details: checked});\n    }\n}"
        ]
      },
      {
        "key": "iconCls",
        "description": "Defines the CSS class of the toggle switch icon; replace the default to restyle the switch.",
        "type": "String",
        "examples": [
          "|iconCls=my-toggle-icon|"
        ],
        "default": "cmn-toggle-icon"
      },
      {
        "key": "id",
        "description": "Defines the id of the checkbox component (`Ext.getCmp(id)`), e.g. to call `toggle()` or `setBoxLabel()`\nfrom a button. Generated when omitted.",
        "type": "String",
        "examples": [
          "|id=show_details|"
        ]
      },
      {
        "key": "inputValue",
        "description": "Defines the DOM `value` attribute of the underlying `<input type=\"checkbox\">` (useful inside an HTML form).",
        "type": "String",
        "examples": [
          "|inputValue=details|"
        ]
      },
      {
        "key": "labelBefore",
        "description": "Set true to render the `text` before (left of) the toggle switch instead of after it.",
        "type": "Boolean",
        "examples": [
          "|labelBefore=true|"
        ],
        "default": "false"
      },
      {
        "key": "notify",
        "description": "Accepted for parity with the map-picking switches (pickpoint, hoverpixel...), where it controls the\nactivation notification. A plain checkbox shows no notification, so the value has no effect here.",
        "type": "Boolean",
        "default": "true"
      },
      {
        "key": "setBoxLabel",
        "description": "Changes the text shown next to the checkbox (the markup `text` parameter) after it was rendered.\nAvailable on every checkbox-style tool (checkbox, pickpoint, hoverpixel, summedarea, areaintegral);\nget the component with `Ext.getCmp(id)`.\n@param boxLabel {String} New label text (HTML allowed).",
        "type": "function(boxLabel)",
        "examples": [
          "Ext.getCmp('my_checkbox').setBoxLabel('Show details (3)');"
        ]
      },
      {
        "key": "text",
        "description": "Defines the text shown next to the toggle switch (the checkbox `boxLabel`).",
        "type": "String",
        "examples": [
          "|text=Show details|"
        ]
      },
      {
        "key": "toggle",
        "description": "Checks or unchecks the checkbox from code, running its `handler` as if the user had clicked it.\nWithout an argument the current state is inverted. Get the component with `Ext.getCmp(id)`.\n@param forceState {Boolean} `true` to check, `false` to uncheck; omit to invert the current state.",
        "type": "function(forceState)",
        "examples": [
          "Ext.getCmp('show_details').toggle(false); // uncheck"
        ]
      },
      {
        "key": "unselect",
        "description": "Accepted for parity with the map-picking switches, where it selects the activation message. A plain\ncheckbox never unchecks itself, so the value has no effect here.",
        "type": "Boolean",
        "default": "true"
      }
    ],
    "Combobox": [
      {
        "key": "_information",
        "completeAPI": "This button is created from <a href='https://docs.sencha.com/extjs/3.4.0/#!/api/Ext.form.ComboBox' target='_blank'>Ext.form.ComboBox</a> for customized properties click on <a href='/api/#category_Combobox'>API here</a>.",
        "description": "It creates an input of ComboBox tool where the value is selectable from a list.\nUsage: '{{combobox}}' or <a href='/elements/combobox-widget/'>examples</a>.",
        "name": "combobox · pick from a list (input)"
      },
      {
        "key": "data",
        "description": "Defines the data that will be displayed in the Combobox.",
        "type": "Array.<Array.<String>>",
        "examples": [
          "|data = [[\"Val_1\"], [\"Val_2\"],..]|"
        ],
        "default": "undefined"
      },
      {
        "key": "editable",
        "description": "Determines if the Combobox is editable. That is, if it allows the user to type inside the input field.\nSet it true to allow it, false otherwise.",
        "type": "Boolean",
        "examples": [
          "|editable=true|"
        ],
        "default": "false"
      },
      {
        "key": "fieldLabel",
        "description": "Defines a label for the Combobox.\nIt will be shown at the left of the Combobox, by default.",
        "type": "String",
        "examples": [
          "|fieldLabel=This is the field label|"
        ]
      },
      {
        "key": "getValue",
        "description": "Returns the currently selected value (the text of the chosen `data` entry). Call it on the component\n(`Ext.getCmp(id)` or a `getid=` reference); `inputs.id[ID]` already holds the same value.",
        "type": "function() : String",
        "examples": [
          "var region = Ext.getCmp('region_combo').getValue();"
        ],
        "returnDescription": "The selected value."
      },
      {
        "key": "hideLabel",
        "description": "Defines if the label of the Combobox should be displayed.\nSet true to hide the label, false to show it.",
        "type": "Boolean",
        "examples": [
          "|hideLabel=true|"
        ],
        "default": "false"
      },
      {
        "key": "id",
        "description": "Defines the id to identify the object.",
        "type": "String",
        "examples": [
          "|id=legend_combobox|"
        ]
      },
      {
        "key": "labelStyle",
        "description": "Defines the style of the label.\nYou can use CSS to style the label element.",
        "type": "String",
        "examples": [
          "|labelStyle=font-weight: bold; color: red;|"
        ]
      },
      {
        "key": "onSelect",
        "description": "Defines a callback run when the user picks an entry. It receives the Ext `select` event arguments\n`(combo, record, index)` — read the chosen text with `record.get('value')` — and `this` is the layer.\nThe value is resolved as a key of the layer `functions` object or, failing that, the text itself is\nevaluated as a function (a body or a full `function(){...}`); unlike the other callbacks it does NOT\nlook for a global (setQueryGlobalProperties) function of that name.\nPS: the layer is recalculated on `select` anyway; use `onSelect` for side effects such as changing\nlayers or updating a window.",
        "type": "function",
        "examples": [
          "|onSelect=onRegionSelected|",
          "functions: {\n    onRegionSelected: function(combo, record, index) {\n        this.changeLayers({name: 'CSR:' + record.get('value'), index: 0});\n    }\n}"
        ]
      },
      {
        "key": "setValue",
        "description": "Selects an entry from code. Pass one of the `data` values; it does not fire `select`, so call\n`forceRecalc()` on an InputManager (or fire the event) when the layer must be recalculated.\n@param value {String} One of the values listed in `data`.",
        "type": "function(value)",
        "examples": [
          "Ext.getCmp('region_combo').setValue('Protected Areas');"
        ]
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[ID]` (and `inputs[i]`): the selected entry of `data` as a string (the first\nentry is selected initially). The layer recalculates on the combobox `select` event (user choice).",
        "type": "String",
        "examples": [
          "{{combobox|id=region_combo|fieldLabel=Region|data=[['Municipalities'],['Protected Areas']]}}",
          "beforeCalc: function(inputs) {\n    var region = inputs.id['region_combo']; // 'Municipalities' or 'Protected Areas'\n}"
        ]
      },
      {
        "key": "width",
        "description": "Defines the width of the combobox in pixels.",
        "type": "Number",
        "examples": [
          "|width=250|"
        ],
        "default": "190"
      }
    ],
    "FileField": [
      {
        "key": "_information",
        "completeAPI": "View all FileField documentation <a href='/api/#category_FileField'>API here</a>.",
        "description": "Tool that allows handle and local files.\nPS: This object cannot be sent to 'expression'.\nUsage: {{filefield|fieldLabel=loadshp|id=loadshp|_onChange=function(){alert('changed')}}}",
        "name": "filefield · open a local file (input)"
      },
      {
        "key": "eventNames",
        "description": "Defines the list of events by name.\nUse this property to define callback functions to any of the following events.",
        "type": "Array.<string>",
        "examples": [
          "['added', 'afterrender', 'beforedestroy', 'beforehide', 'beforerender', 'beforeshow', 'beforestaterestore',\n'beforestatesave', 'blur', 'change', 'destroy', 'disable', 'enable', 'focus', 'hide', 'invalid', 'move', 'removed', 'render',\n'resize', 'show', 'specialkey', 'staterestore', 'statesave', 'valid']"
        ]
      },
      {
        "key": "fieldLabel",
        "description": "Defines the label shown at the left of the file input (Ext `fieldLabel`). `hideLabel=true` removes the\nlabel and its reserved space; other `Ext.form.Field` configs are passed through unchanged.",
        "type": "String",
        "examples": [
          "|fieldLabel=Load a shapefile (.zip)|"
        ]
      },
      {
        "key": "hideLabel",
        "description": "Set true to hide the label element and the space reserved for it.",
        "type": "Boolean",
        "examples": [
          "|hideLabel=true|"
        ],
        "default": "false"
      },
      {
        "key": "id",
        "description": "Defines the id of the input; it is the key used in `inputs.id[ID]` and the id of the Ext field\n(`Ext.getCmp(id)`), so it must be unique in the page.",
        "type": "String",
        "examples": [
          "|id=loadshp|"
        ]
      },
      {
        "key": "ignoreUpdate",
        "description": "Defines if it should ignore the widget change event.\nIf it's false, it dispatches the update event at every file selection.",
        "type": "Boolean",
        "default": "false"
      },
      {
        "key": "inputType",
        "description": "The HTML input type. It is always forced to `file` by the tool, so a value written in the markup is\nignored (documented only to explain why it cannot be changed).",
        "type": "String",
        "default": "file"
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[ID]`: not the file itself but a getter function. Call it —\n`inputs.id[ID]()` — to obtain the selected `File` (the first entry of the DOM `files` list) or `undefined`\nwhen nothing is selected. It is a function so the value can never be serialized to the calculation\nWebWorker (`expression`); read it in `beforeCalc`, in a `_onchange=` callback or in a button handler.\nThe layer recalculates on the `change` event of the input (a new file chosen) unless\n`ignoreUpdate=true`.",
        "type": "function",
        "examples": [
          "{{filefield|fieldLabel=loadshp|id=loadshp|_onChange=onSelectFile}}",
          "functions: {\n    onSelectFile: function() {\n        var file = this.getInputs().id['loadshp']();\n        if (file) ExtjsUtils.GEOJSON.shapefile2GeojsonAsync(file, {layer: this, fromProj: 'EPSG:4326'});\n    }\n}"
        ]
      }
    ],
    "Hoverpixel": [
      {
        "key": "_information",
        "completeAPI": "View the complete HoverPixel <a href='/api/#category_Hoverpixel'>API here</a>.",
        "description": "Create a tool to instantly inspect pixel under mouse. The value of the map can also be used as input for another functions.\nUsage: {{hoverpixel}}",
        "name": "hoverpixel · value under the mouse (input)"
      },
      {
        "key": "checked",
        "description": "Defines if the hoverPixel should start enabled.\nSet true to start enabled, false otherwise.",
        "type": "Boolean",
        "examples": [
          "|checked = true|"
        ],
        "default": "false"
      },
      {
        "key": "fieldLabel",
        "description": "Define the text that will be displayed at the left of the toggler",
        "type": "String",
        "examples": [
          "|text=This is the label text|"
        ]
      },
      {
        "key": "getLayerValues",
        "description": "Reads the values of the composed map under a mouse event: one value per inner layer (from the\nlegend colours of the rendered tiles) plus one extra entry with the result of the layer\n`expression` for those values. This is the `layerVals` argument of `runOnClick`/`runOnHover`;\ncall it from code (`Ext.getCmp(id).items.get(0).getLayerValues(evt, layer)`) to sample the map\nfrom any mouse event. Returns `null` when the layer is hidden, the legend is not loaded yet, or the\nmouse is outside the map data (all layers null/transparent).\n@param evt {MouseEvent} Mouse event with the screen position to sample.\n@param layer {OpenLayers.Layer.Composed} The composed layer to read (normally the tool's own layer).",
        "type": "function(evt, layer) : Array.<Number>|null",
        "returnDescription": "`[value of layer 0, ..., value of layer n-1, expression result]`, or `null`."
      },
      {
        "key": "hideLabel",
        "description": "Defines if the label of the HoverPixel should be displayed.\nSet true to hide the label, false to show it.",
        "type": "Boolean",
        "examples": [
          "|hideLabel=true|"
        ],
        "default": "false"
      },
      {
        "key": "iconCls",
        "description": "Defines the CSS class of the toggle switch icon; replace the default to restyle the switch.",
        "type": "String",
        "examples": [
          "|iconCls=my-toggle-icon|"
        ],
        "default": "cmn-toggle-icon"
      },
      {
        "key": "id",
        "description": "Defines the id to identify the object.",
        "type": "String",
        "examples": [
          "|id=exemple_hover_pixel|"
        ]
      },
      {
        "key": "labelBefore",
        "description": "Set true to render the `text` label before (left of) the toggle switch instead of after it.",
        "type": "Boolean",
        "examples": [
          "|labelBefore=true|"
        ],
        "default": "false"
      },
      {
        "key": "labelStyle",
        "description": "Defines the style of the label at the HoverPixel toggler.\nYou can use CSS to style the label element.",
        "type": "String",
        "examples": [
          "|labelStyle=font-weight: bold; color: red;|"
        ]
      },
      {
        "key": "notify",
        "description": "Set false to suppress the notification shown when the tool is activated (\"click on the map...\").",
        "type": "Boolean",
        "examples": [
          "|notify=false|"
        ],
        "default": "true"
      },
      {
        "key": "runOnClick",
        "description": "Defines a callback when the user clicks on the map.\n\nIt passes the following parameters for the callback function:\nhandleOnClick(layerVals, inputs, coordinates, clickEvent, lastCoordinates)\n@param layerVals {Array} Array with the values of the maps at the pixel that was clicked.\n@param inputs {Array} Array with the values of the inputs defined in the descriptionHtml.\n@param coordinates {OpenLayers.LonLat} The point that was clicked, in the map's projection (Web\n Mercator, metres) despite the names: `lon` is x and `lat` is y. For degrees, transform it:\n `coordinates.clone().transform(ExtjsUtils.JS.getMap().getProjectionObject(), new OpenLayers.Projection(\"EPSG:4326\"))`\n (lastCoordinates below already holds degrees, of the previous click and hover).\n@param clickEvent {MouseEvent} Mouse event that triggered the function.\n@param lastCoordinates {Object} Object with the last coordinates (latitude, longitude) from the last click and the last hover events.\n lastCoordinates = {\n     click: {\n           lat: // Latitude of the last click\n           lon: // Longitude of the last click\n     },\n     hover: {\n           lat: // Latitude of the last hover\n           lon: // Longitude of the last hover\n     }\n }",
        "type": "function",
        "examples": [
          "|handleOnClick: function(layerVals, inputs, coordinates, clickEvent, lastCoordinates) {\n     var degrees = coordinates.clone().transform(ExtjsUtils.JS.getMap().getProjectionObject(), new OpenLayers.Projection(\"EPSG:4326\"));\n     ExtjsUtils.ALERTIFY.log(\"Latitude: \" + degrees.lat.toFixed(3) + \" Longitude: \" + degrees.lon.toFixed(3));\n}|"
        ],
        "default": "undefined"
      },
      {
        "key": "runOnClickOutside",
        "description": "True to run the callback function even when clicking outside of the layer, False to disable. (Default False)",
        "type": "function"
      },
      {
        "key": "runOnHover",
        "description": "Defines a callback when the user hovers the map.\n\nIt passes the following parameters for the callback function:\nhandleOnClick(layerVals, inputs, coordinates, clickEvent, lastCoordinates)\n@param layerVals {Array} Array with the values of the maps at the pixel that was hovered.\n@param inputs {Array} Array with the values of the inputs defined in the descriptionHtml.\n@param coordinates {OpenLayers.LonLat} The point that was hovered, in the map's projection (Web\n Mercator, metres) despite the names: `lon` is x and `lat` is y. For degrees, transform it:\n `coordinates.clone().transform(ExtjsUtils.JS.getMap().getProjectionObject(), new OpenLayers.Projection(\"EPSG:4326\"))`\n (lastCoordinates below already holds degrees, of the previous click and hover).\n@param mouseMoveEvent {MouseEvent} Mouse event that triggered the function.\n@param lastCoordinates {Object} Object with the last coordinates (latitude, longitude) from the last click and the last hover events.\n lastCoordinates = {\n     click: {\n           lat: // Latitude of the last click\n           lon: // Longitude of the last click\n     },\n     hover: {\n           lat: // Latitude of the last hover\n           lon: // Longitude of the last hover\n     }\n }",
        "type": "function",
        "examples": [
          "|handleOnHover: function(layerVals, inputs, coordinates, mouseMoveEvent, lastCoordinates) {\n     ExtjsUtils.ALERTIFY.log(\"Latitude: \" + coordinates.lat + \" Longitude: \" + coordinates.lon);\n}|"
        ],
        "default": "undefined"
      },
      {
        "key": "runOnHoverOutside",
        "description": "True to run the callback function even when hovering outside of the layer, False to disable. (Default False)",
        "type": "function"
      },
      {
        "key": "text",
        "description": "Define the text that will be displayed at the right of the toggler",
        "type": "String",
        "examples": [
          "|text=This is the example text|"
        ]
      },
      {
        "key": "unselect",
        "description": "Only changes the activation message: with the default `true` it says a single point is expected,\nwith `false` several. The hoverpixel is never deactivated automatically after a click.",
        "type": "Boolean",
        "examples": [
          "|unselect=false|"
        ],
        "default": "true"
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[ID]`: the `lastInfo` object `{click, hover}` with the coordinates\n(`{lon, lat}` in EPSG:4326, or `null` before the first event) of the last click and of the last mouse\nmove while the tool is active. The object is updated in place, so it is always current when read in\n`beforeCalc`/`expression`; it is also the last argument of `runOnClick`/`runOnHover` (inside the\ncallback it still holds the previous event — the current one is written right after the callback\nreturns; use the `coordinates` argument for the current position). The input is registered on the\n`forceupdatelayer` event of the widget, which nothing fires by itself — moving the mouse or clicking\ndoes not recalculate the layer. React inside `runOnClick`/`runOnHover` (which also receive the map\nvalues) or trigger the recalculation yourself (e.g. `forceRecalc()` on an InputManager).",
        "type": "Object",
        "examples": [
          "{{hoverpixel|id=hover_tool|text=Inspect values|runOnClick=onMapClick}}",
          "functions: {\n    onMapClick: function(layerVals, inputs, coords, evt, lastInfo) {\n        console.log(coords, lastInfo.click, inputs.id['hover_tool'] === lastInfo); // current, previous, true\n    }\n}"
        ]
      }
    ],
    "InputManager": [
      {
        "key": "_information",
        "completeAPI": "View the complete InputManager <a href='/api/#category_InputManager'>API here</a>.",
        "description": "Tool to store local variables to be used in others functions callbacks.\nPS: DOM elements cannot be used in 'expression' context, because them cannot be sent to WebWorkers (javascript language limitation).\nUsage: '{{inputmanager}}'",
        "name": "inputmanager · values kept for your functions (input)"
      },
      {
        "key": "forceRecalc",
        "description": "Force a legend map recalculation.",
        "type": "function()"
      },
      {
        "key": "getValue",
        "description": "Get the stored value by his property name, if it does not exists returns null.\n@param key {String} Stored property name.",
        "type": "function(key) : *",
        "returnDescription": "Desired stored property when it exists or null otherwise."
      },
      {
        "key": "id",
        "description": "Defines the id of the input; it is the key used to reach the manager in `inputs.id[ID]` (required,\nthe tool renders nothing visible).",
        "type": "String",
        "examples": [
          "|id=state|"
        ]
      },
      {
        "key": "setDefaultValues",
        "description": "Set default values to the stored elements, these values are used before any other value is defined\nand never update or replace another stored values.\n\nPS: Auxiliary function to make easy wrinting the script (typically called in `onInputsReady` or at\nthe start of `beforeCalc`).\nPS: This function never fire layer update.\n@param obj {Object} Default values properties.\n@param local {Boolean} True when the properties should be store locally (and not sent to expression\n WebWorkers callbacks).",
        "type": "function(obj, local)",
        "examples": [
          "inputs.id['state'].setDefaultValues({year: 2020, scenario: 'base'});"
        ]
      },
      {
        "key": "setValues",
        "description": "Stores object properties for later usage.\n\nPS: If has name property collision the older is replaced.\nPS: Values go to one of two buckets of the manager: `global` (the default — serialized and sent\nto `expression`, so it must hold plain JSON-compatible data) or `_local_` (with `local=true` — kept\nin the browser only, may hold DOM elements, Ext components, functions or circular objects).\n`getValue(key)` looks in `global` first, then in `_local_`.\n@param obj {Object} Object with the properties to be stored.\n@param cancelUpdate {Boolean} False if this change must cause layer recalculation, True otherwise.\n@param local {Boolean} True to store the property locally, False otherwise. The local properties aren't sent to 'expression' calbacks. (i.e. If a element is recursive or have DOM elements, it must local avoid stringify errors on 'expression' callbacks)",
        "type": "function(obj, cancelUpdate, local)",
        "examples": [
          "inputs.id['state'].setValues({year: 2020});                      // recalculates the layer",
          "inputs.id['state'].setValues({lastInterval: cur}, true, true);  // silent, browser-only"
        ]
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[ID]`: the manager object itself, with `getValue(key)`, `setValues(obj,\ncancelUpdate, local)`, `setDefaultValues(obj, local)` and `forceRecalc()` (listed in this group) and the\n`global` bucket where the stored properties live (`inputs.id[ID].global.key` is a shortcut for\n`getValue`). The layer recalculates on its own `change` event, which `setValues` (unless cancelled) and\n`forceRecalc` trigger. Only the `global` bucket reaches `expression` (WebWorker); values stored with\n`local=true` are kept in a bucket that is not serialized and can therefore hold DOM/Ext references.",
        "type": "Object",
        "examples": [
          "{{inputmanager|id=state}}",
          "beforeCalc: function(inputs) {\n    var year = inputs.id['state'].getValue('year') || 2020;\n}"
        ]
      }
    ],
    "Label": [
      {
        "key": "_information",
        "completeAPI": "This label is created from <a href='https://docs.sencha.com/extjs/3.4.0/#!/api/Ext.form.Label' target='_blank'>Ext.form.Label</a>.\nOnly some properties are <a href='/api/#category_Label'>listed here</a>.",
        "description": "Create a simple label element to display some text.\nUsage: {{label|}}",
        "name": "label · text"
      },
      {
        "key": "cls",
        "description": "Extra CSS class(es) added to the `<label>` element.",
        "type": "String",
        "examples": [
          "{{label|cls=slider_label|text=0%}}"
        ]
      },
      {
        "key": "forId",
        "description": "Defines the id of the form field the label is for (rendered as the `for` attribute of the\n`<label>`), so clicking the label focuses that field.",
        "type": "String",
        "examples": [
          "{{label|text=Price (US$)|forId=soy_value}}{{textfield|id=soy_value|isnumeric}}"
        ]
      },
      {
        "key": "html",
        "description": "Defines the content of the label as raw HTML (not escaped). Everything after `html=` up to the next\n`|` is the value, so it may contain `=` characters. `text` wins when both are given.",
        "type": "String",
        "examples": [
          "{{label|cls=slider_label|html=0% <b>...</b> 100%|id=scale_label}}"
        ]
      },
      {
        "key": "id",
        "description": "Defines the id of the label component (`Ext.getCmp(id)`), needed to change its text from code or\nto reference it with `getid=` in another tag. Generated when omitted.",
        "type": "String",
        "examples": [
          "{{label|id=deforestation_label|text=20%}}"
        ]
      },
      {
        "key": "style",
        "description": "Inline CSS applied to the `<label>` element. Any other `Ext.form.Label`/`Ext.Component` config\n(`hidden`, `width`...) is passed through unchanged.",
        "type": "String",
        "examples": [
          "{{label|text=Total|style=font-weight: bold; color: #336699;}}"
        ]
      },
      {
        "key": "text",
        "description": "Defines the text of the label. It is HTML-escaped, so use `html` when markup is needed. Update it\nlater with `Ext.getCmp(id).setText(text)` (typically from an `on_change=` listener of a slider or\ntextfield through `getid=`).",
        "type": "String",
        "examples": [
          "{{label|id=deforestation_label|text=Deforestation: 20%}}"
        ]
      }
    ],
    "LegendHtml": [
      {
        "key": "_information",
        "description": "Create a tool with the map legends at any place of the query description.\nUsage: '{{legendhtml}}'",
        "name": "legendhtml · legend of the calculated map"
      },
      {
        "key": "autoWidth",
        "description": "Set true (default) to let the legend panel take the width of its container; set false to give it a\nfixed `width`.",
        "type": "Boolean",
        "examples": [
          "|autoWidth=false|width=250|"
        ],
        "default": "true"
      },
      {
        "key": "filterLayers",
        "description": "Defines an array of indexes of layers to be included in map legend (from 0 to quantity of layers).\nIf not defined, all layer legends are shown by default. Otherwise, only the listed indexes are included.\n\nEx: A composed layer with three maps:\nname: \"CSR:estados,CSR:roads,CSR:municipalities\",\nIf 'filterLayers=[0,1]' is defined in the layer object only the legends of 'CSR:estatdos' and 'CSR:roads' are shown.",
        "type": "Array.<Number>",
        "examples": [
          "|filterLayers = [1,2]|"
        ],
        "default": "null"
      },
      {
        "key": "id",
        "description": "Defines the id of the legend panel component (`Ext.getCmp(id)`), e.g. to `show()`/`hide()` it or to\nfind the legend entries inside it. Generated when omitted. Other `GeoExt.WMSLegend`/`Ext.Panel`\nconfigs (`cls`, `style`, `hidden`, `useScaleParameter`, `autoWidth`...) are passed through.",
        "type": "String",
        "examples": [
          "|id=main_legend|"
        ]
      },
      {
        "key": "legendId",
        "description": "Defines the legend container id. You can use this id to toggle each legend filter individually.",
        "type": "String",
        "examples": [
          "|legendId=WIDGET_OBJECT_ID|"
        ],
        "default": "null"
      },
      {
        "key": "preventClick",
        "description": "Defines if the user can filter the maps categories by clicking on the legend.\nSet it true to ignore the legend click, false otherwise.",
        "type": "Boolean",
        "examples": [
          "|preventClick = true|"
        ],
        "default": "false"
      },
      {
        "key": "reverseLegend",
        "description": "Defines if it should sort the legend on the decreasing order.\nSet it true to use the decreasing order, false otherwise.",
        "type": "Boolean",
        "examples": [
          "|reverseLegend = true|"
        ],
        "default": "false"
      },
      {
        "key": "useScaleParameter",
        "description": "Set true to request a new legend image from the server whenever the map scale changes (GeoServer\n`SCALE` parameter), for styles that depend on the scale. Off by default: the legend is generated once\nand reused, which is faster and keeps the click-to-filter behaviour stable.",
        "type": "Boolean",
        "examples": [
          "|useScaleParameter=true|"
        ],
        "default": "false"
      }
    ],
    "LiveComposedSplit": [
      {
        "key": "_information",
        "examples": [
          "{{livecomposedsplit|id=precip_split|displayNames=January,August|layerNames=precip_monthly_average_1,precip_monthly_average_8|baseName=CSR:precip_monthly_average|leftDefault=January|rightDefault=August}}",
          "{{livecomposedsplit|id=estados_split|displayNames=e1,e2,e3|layerNames=estados_1,estados_2,estados_3|baseName=CSR:estados|leftDefault=e1|rightDefault=e3}}"
        ],
        "completeAPI": "View the complete LiveComposedSplit <a href='/api/#category_LiveComposedSplit'>API here</a>.",
        "description": "Compare two styles of a Composed layer with a live split-screen slider on the map.\n\nThe layer must be a Composed layer (typically the same WMS layer twice in `name` with two `styles`). Put the tool in `descriptionHtml`, open the query panel, choose left/right styles, then press Compare to show a draggable vertical divider.\n\nRequired parameters: displayNames, layerNames (same order and length). Recommended: id, baseName, leftDefault, rightDefault. Optional: layout=compact (default, stacked for the layer card) or layout=inline (legacy single row).\n\nUsage: {{livecomposedsplit|id=...|displayNames=...|layerNames=...|baseName=...|leftDefault=...|rightDefault=...}}",
        "name": "livecomposedsplit · split-screen comparison"
      },
      {
        "key": "applySelectedStyles",
        "description": "Pushes the two selected styles to the composed layer (`changeLayers` with the left style on child 0 and\nthe right style on child 1) and makes both children visible. Called when Compare is pressed; after\ncalling it yourself use `refreshActiveSplit()` so the divider clips the new child divs.",
        "type": "function()",
        "examples": [
          "Ext.getCmp('precip_split_container').applySelectedStyles();"
        ]
      },
      {
        "key": "baseName",
        "description": "WMS layer name used with each style (e.g. CSR:precip_monthly_average).\nDefaults to layer.params.LAYERS when omitted.",
        "type": "String",
        "examples": [
          "|baseName=CSR:precip_monthly_average|"
        ]
      },
      {
        "key": "cls",
        "description": "Extra CSS class for the Compare toggle button (replaces the default `clickable`; the class\n`live-composed-split-compare` is always added).",
        "type": "String",
        "examples": [
          "|cls=my-compare-button|"
        ]
      },
      {
        "key": "composedLayerName",
        "description": "Name of the Composed layer to split. If omitted, the first\nOpenLayers.Layer.Composed on the map is used.",
        "type": "String",
        "examples": [
          "|composedLayerName=CSR:precip_monthly_average|"
        ]
      },
      {
        "key": "destroySplitControl",
        "description": "Removes the split divider control from the map for good (it is recreated on the next Compare press).\nThe panel calls it when its layer is removed; call it to force a clean state. It does not restore the\nsingle-style view — press the Compare button off (`toggle(false)`) for the normal close.",
        "type": "function()",
        "examples": [
          "Ext.getCmp('precip_split_container').destroySplitControl();"
        ]
      },
      {
        "key": "displayNames",
        "description": "Comma-separated labels shown in the left/right combo boxes (required).",
        "type": "String",
        "examples": [
          "|displayNames=January,February,March,April,May,June,July,August,September,October,November,December|"
        ]
      },
      {
        "key": "id",
        "description": "Id of the Compare button (wrapper id is id + \"_container\").",
        "type": "String",
        "examples": [
          "|id=my_composed_split|"
        ]
      },
      {
        "key": "isComparisonActive",
        "description": "Tells whether the split comparison is currently shown on the map (Compare pressed and the divider\nactive). Get the panel with `Ext.getCmp(id + '_container')`, where `id` is the markup id.",
        "type": "function() : Boolean",
        "examples": [
          "if (Ext.getCmp('precip_split_container').isComparisonActive()) { ... }"
        ],
        "returnDescription": "`true` while the two styles are being compared."
      },
      {
        "key": "layerNames",
        "description": "Comma-separated WMS style names aligned with displayNames (required).\nWhen Compare is pressed, the composed layer switches to the selected pair.",
        "type": "String",
        "examples": [
          "|layerNames=precip_monthly_average_1,precip_monthly_average_2,...|"
        ]
      },
      {
        "key": "layout",
        "description": "Presentation inside the layer query panel.\ncompact (default): stacked fields that fit the ~300px layer card.\ninline: horizontal row (legacy Left / Right / Compare on one line).",
        "type": "String",
        "examples": [
          "|layout=inline|"
        ],
        "default": "compact"
      },
      {
        "key": "leftDefault",
        "description": "Initial left combo value; must be one of displayNames.",
        "type": "String",
        "examples": [
          "|leftDefault=January|"
        ]
      },
      {
        "key": "refreshActiveSplit",
        "description": "Re-applies the selected styles and re-clips the split after the composed layer's children were replaced\n(e.g. by your own `changeLayers` call), keeping the divider position. Does nothing while the comparison\nis not active.",
        "type": "function()",
        "examples": [
          "Ext.getCmp('precip_split_container').refreshActiveSplit();"
        ]
      },
      {
        "key": "rightDefault",
        "description": "Initial right combo value; must be one of displayNames (and different from left).",
        "type": "String",
        "examples": [
          "|rightDefault=August|"
        ]
      },
      {
        "key": "setComparisonEditingEnabled",
        "description": "Enables or disables the right-side combo and the swap button. The panel calls it itself (only the left\ncombo is editable while idle; both unlock when comparing); call it to lock the choice while data loads.\n@param enabled {Boolean} `true` to allow changing the right side and swapping, `false` to lock them.",
        "type": "function(enabled)",
        "examples": [
          "Ext.getCmp('precip_split_container').setComparisonEditingEnabled(false);"
        ]
      },
      {
        "key": "swapSides",
        "description": "Exchanges the left and right styles (combos and map) and re-clips the split, keeping the divider where\nit is. Does nothing while the comparison is not active — what the swap button does.",
        "type": "function()",
        "examples": [
          "Ext.getCmp('precip_split_container').swapSides();"
        ]
      },
      {
        "key": "text",
        "description": "Label on the Compare toggle button (before the split is active).",
        "type": "String",
        "examples": [
          "|text=Compare Monthly Data|"
        ],
        "default": "Compare Layers"
      }
    ],
    "LoadCsv": [
      {
        "key": "_information",
        "completeAPI": "View the complete LoadCSV <a href='/api/#category_LoadCsv'>API here</a>.",
        "description": "Describes the API to read and manipulate ExtjsUtils.CSV.CsvTable files from URL.",
        "name": "loadcsv · load a CSV table (input)"
      },
      {
        "key": "columnNameToInd",
        "description": "Get the index of a column with the 'columnName' name.\n@param columnName {String} Column name to search for.",
        "type": "function(columnName) : Number",
        "returnDescription": "When it exists returns the column index, otherwise -1."
      },
      {
        "key": "columnNamesToIndexes",
        "description": "Resolves column names to column indexes: every string entry is looked up in the\nheader (`-1` when absent) and every numeric entry is kept as it is. A single value\nis accepted in place of the array. This is what `getLines`/`createIndexes` do with\ntheir `columns` argument.\n@param columnNames {Array.<(String|Number)>|String|Number} Column names and/or indexes.",
        "type": "function(columnNames) : Array.<Number>",
        "examples": [
          "inputs.id[\"fire_csv\"].columnNamesToIndexes([\"Year\", 2]); // e.g. [1, 2]"
        ],
        "returnDescription": "The column indexes, in the same order."
      },
      {
        "key": "cors",
        "description": "Downloads the CSV through the Mappia CORS proxy; use it for servers that do not send CORS headers.",
        "type": "Boolean",
        "examples": [
          "|cors=true|"
        ],
        "default": "false"
      },
      {
        "key": "createIndexes",
        "description": "Create indexes for faster search.\n\nPS: Indexes are used to faster results on \"getLines\" calls, apply only when all [columns] are indexes.\n@param columns {Array} (Optional) Array of indexes/names of the filtered columns.",
        "type": "function(columns)",
        "examples": [
          "{...\n    beforeCalc: function(inputs) {\n        inputs.id['CSV_WIDGET_EXAMPLE_ID'].createIndexes(['Key', 'Year']);\n        alert(inputs.id['CSV_WIDGET_EXAMPLE_ID'].getLines(['Key','Year'], [100, 2020]).length);\n    }\n}"
        ]
      },
      {
        "key": "getColunsInd",
        "description": "Returns a copy of the header row — the column names, in order — so query code can\ndiscover columns instead of hard-coding indexes. The name is a historical typo of\n`getColumnsInd`, kept for compatibility (the platform and many queries call it by\nthis spelling; there is no correctly spelled alias).",
        "type": "function() : Array.<String>",
        "examples": [
          "var headers = inputs.id[\"fire_csv\"].getColunsInd(); // [\"Municipality\", \"Year\", \"Fires\"]\nvar iFires = headers.indexOf(\"Fires\");"
        ],
        "returnDescription": "Copy of the header row."
      },
      {
        "key": "getLineCount",
        "description": "Number of data lines in the table (the header line is not counted).",
        "type": "function() : Number",
        "examples": [
          "var csv = inputs.id[\"fire_csv\"];\nfor (var i = 0; i < csv.getLineCount(); i++) total += parseFloat(csv.getValue(2, i));"
        ],
        "returnDescription": "How many data lines the CSV has."
      },
      {
        "key": "getLines",
        "description": "Returns the lines (arrays of cell strings) whose cells in `columns` equal the\ncorresponding entries of `values`. Columns may be given by index or by header name;\nto filter on several columns give one value per column, e.g.\n`getLines([1, \"Year\"], [\"Park A\", 2024])`. Values are compared as strings (the CSV\nis always text). Called with no arguments it returns every data line (header\nskipped) — a common idiom. When all filtered columns were indexed with\n`createIndexes` the lookup uses the index instead of scanning every line.\n@param columns {Array.<(String|Number)>} Indexes and/or names of the columns to filter on.\n@param values {Array} One value per entry of `columns`.\n@param includeHeader {Boolean} True to also return the header line when it matches (only in the scanning path, i.e. without indexes).",
        "type": "function(columns, values, includeHeader) : Array.<Array.<String>>",
        "examples": [
          "var csv = inputs.id[\"fire_csv\"];\nvar rows2024 = csv.getLines([\"Year\"], [2024]);      // by header name\nvar rowsParkA = csv.getLines([0, \"Year\"], [\"Park A\", 2024]); // two columns\nvar allRows = csv.getLines();                         // every data line"
        ],
        "returnDescription": "The matching lines; all data lines when no filter is given."
      },
      {
        "key": "getValue",
        "description": "Get a value by the matrix index and column.\n@param column {Number} Column index (First index is 0).\n@param line {Number} Line index (First index is 0).\n@param includeHeader {boolean} True to include the header line in the matrix index, False to ignore.",
        "type": "function(column, line, includeHeader) : String",
        "returnDescription": "Get the cell value."
      },
      {
        "key": "id",
        "description": "Defines the id of the input; it is the key used to read the table in `inputs.id[ID]` (required, the\ntool renders nothing visible).",
        "type": "String",
        "examples": [
          "|id=emissions_csv|"
        ]
      },
      {
        "key": "removeEmptyLines",
        "description": "Ignore the empty lines, removing them from the parsed CSV. True to remove the\nempty lines from the CSV.",
        "type": "boolean",
        "default": "false"
      },
      {
        "key": "setValue",
        "description": "Change a cell value by its cell index.\n@param column {Numeric} Column index.\n@param line {Numeric} Line index to change (ignore the header information when it exists).\n@param value {*} New value to replace the older value.",
        "type": "function(column, line, value)"
      },
      {
        "key": "trim",
        "description": "Requests that cell values be trimmed of surrounding whitespace. The flag is accepted and forwarded to\nthe CSV parser, but the current parser ignores it (cells are stored as written, quotes removed), so\ntrim values yourself when needed. Kept for compatibility with existing queries.",
        "type": "Boolean",
        "examples": [
          "|trim=true|"
        ],
        "default": "false"
      },
      {
        "key": "url",
        "description": "Defines the URL of the CSV file to download (required). Relative URLs are resolved against the Mappia\nhost, so backend endpoints such as `/wmtp/calc/...` work; escape `=` inside query strings as `\\=`.\nThe layer waits for the download before calculating.",
        "type": "String",
        "examples": [
          "|url=/theme/app/data/emissoesco2.csv|",
          "|url=/wmtp/calc/areacategorical/?layers\\=CSR:estados&styles\\=1|"
        ]
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[ID]`: an `ExtjsUtils.CSV.CsvTable` wrapping the parsed file (first row =\nheader). Read it with the CsvTable methods listed in this group (`getLines`, `getValue`, `columnNameToInd`,\n`createIndexes`, `getLineCount`...). It is `undefined` until the download finishes: the layer waits for the resource and\nrecalculates on its `waitend` event, so `beforeCalc`/`expression` can rely on it being loaded.",
        "type": "ExtjsUtils.CSV.CsvTable",
        "examples": [
          "{{loadcsv|id=emissions_csv|url=/theme/app/data/emissoesco2.csv|removeEmptyLines=true}}",
          "beforeCalc: function(inputs) {\n    var csv = inputs.id['emissions_csv'];          // columns: percentage, carbon_loss\n    var rows = csv.getLines(0, '25');               // rows whose first column equals 25\n}"
        ]
      }
    ],
    "LoadJson": [
      {
        "key": "_information",
        "completeAPI": "",
        "description": "Tool to load a remote JSON and use it with map, this object can store string, values and functions.\nUsage: {{loadjson}}",
        "name": "loadjson · load JSON (input)"
      },
      {
        "key": "cors",
        "description": "Loads the JSON through the Mappia CORS proxy (for servers without CORS headers).",
        "type": "Boolean",
        "examples": [
          "|cors=true|"
        ],
        "default": "false"
      },
      {
        "key": "id",
        "description": "Defines an id for the stored info on layerInputs.",
        "type": "String",
        "examples": [
          "|id=window-div-id|",
          "on javascript: layer.getInputs().id[\"NAME_DEFINED_HERE\"]"
        ]
      },
      {
        "key": "url",
        "description": "Defines the url to load the json from.",
        "type": "String",
        "examples": [
          "|url=https://maps.csr.ufmg.br/theme/app/data/conab/limites_municipios_conab.geojson|"
        ]
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[ID]`: the parsed JSON (`JSON.parse` of the response body — an object or an\narray; an empty response gives `[]`). It is `undefined` until the download finishes: the layer waits\nfor the resource and recalculates on its `waitend` event, so `beforeCalc`/`expression` can rely on it\nbeing loaded.",
        "type": "Object|Array",
        "examples": [
          "{{loadjson|id=limits|url=https://maps.csr.ufmg.br/theme/app/data/example.geojson}}",
          "beforeCalc: function(inputs) {\n    var geojson = inputs.id['limits'];\n    console.log(geojson.features.length);\n}"
        ]
      }
    ],
    "OpacitySlider": [
      {
        "key": "_information",
        "description": "Create a slider to change the map opacity.\nUsage: '{{opacityslider}}'",
        "name": "opacityslider · layer opacity"
      },
      {
        "key": "aggressive",
        "description": "Set true to apply the opacity while the thumb is being dragged instead of only when it is released.",
        "type": "Boolean",
        "examples": [
          "|aggressive=true|"
        ],
        "default": "false"
      },
      {
        "key": "changeVisibility",
        "description": "Set true to let the slider also control the layer visibility: the layer is hidden when the slider\nreaches `minValue` and shown again when it leaves it. The layer must be visible when the slider is\ncreated.",
        "type": "Boolean",
        "examples": [
          "|changeVisibility=true|"
        ],
        "default": "false"
      },
      {
        "key": "complementaryLayer",
        "description": "A second layer that is hidden when the slider reaches `maxValue` (GeoExt option to fade between two\nlayers). GeoExt expects a layer object here, which the markup cannot express (a string is not\nresolved to a layer), so this option is only usable from code; documented for completeness.",
        "type": "OpenLayers.Layer"
      },
      {
        "key": "id",
        "description": "Defines the id of the slider component (`Ext.getCmp(id)`), e.g. to read `getValue()` or to `hide()` it.\nGenerated when omitted. Every parameter of the tool is passed to `GeoExt.LayerOpacitySlider` /\n`Ext.slider.SingleSlider` unchanged (`width`, `cls`, `vertical`, `hidden`...).",
        "type": "String",
        "examples": [
          "|id=opacity|"
        ]
      },
      {
        "key": "inverse",
        "description": "Set true to make the slider work with transparency instead of opacity (100 = fully transparent).",
        "type": "Boolean",
        "examples": [
          "|inverse=true|"
        ],
        "default": "false"
      },
      {
        "key": "maxValue",
        "description": "Defines the maximum value of the slider (opacity percent at the right end).",
        "type": "Number",
        "examples": [
          "|maxValue=90|"
        ],
        "default": "100"
      },
      {
        "key": "minValue",
        "description": "Defines the minimum value of the slider (opacity percent at the left end).",
        "type": "Number",
        "examples": [
          "|minValue=10|"
        ],
        "default": "0"
      },
      {
        "key": "value",
        "description": "Defines the initial slider value (opacity in percent). It is only used when the layer has no opacity\nyet (`opacity` not set on the layer): otherwise the slider starts at the layer's current opacity.",
        "type": "Number",
        "examples": [
          "|value=50|"
        ],
        "default": "100"
      }
    ],
    "PickPoint": [
      {
        "key": "_information",
        "completeAPI": "View the complete PickPoint <a href='/api/#category_PickPoint'>API here</a>",
        "description": "Creates an input that allows users to interact with the real cell or feature value at any position.\n\nThe returned values are from the original mal to the selected point,\n in case of RASTER is returned a cell value, in case of shapefile the geometry is returned too.\nUsage: {{pickpoint}}",
        "name": "pickpoint · pick a point on the map (input)"
      },
      {
        "key": "checked",
        "description": "Defines if Pickpoint should start selected.\nSet it true to start it enabled, false otherwise.",
        "type": "Boolean",
        "examples": [
          "|checked = false|"
        ],
        "default": "false"
      },
      {
        "key": "fieldLabel",
        "description": "Sets the label for the 'pickpoint' button widget.",
        "type": "String",
        "examples": [
          "'Pick a point coordinate (Lat,Lon)'"
        ]
      },
      {
        "key": "geometryColor",
        "description": "If defined sets a color to use when drawing a geometry by this tool. Otherwise a random color will be used at each interaction.",
        "type": "String",
        "examples": [
          "|geometryColor=#FF00FF|"
        ],
        "default": "undefined"
      },
      {
        "key": "getAttributes",
        "description": "Returns the attributes of every selected point for one of the layers of the composed map: one\nobject per selection (in click order) holding the feature `data` of that layer, or `{}` when the\nclick hit nothing on it (e.g. a raster). This is the usual way to read the selection in\n`beforeCalc`.\n@param mapIndex {Number} 0-based index of the layer inside the composed layer (`name` order).",
        "type": "function(mapIndex) : Array.<Object>",
        "examples": [
          "beforeCalc: function(inputs) {\n    var picked = inputs.id['property_pick'].getAttributes(1);\n    var names = picked.map(function(attrs) { return attrs.Name; });\n}"
        ],
        "returnDescription": "Attribute objects of the selected features on that layer."
      },
      {
        "key": "getLastEvent",
        "description": "Returns the map click event handled by the last selection (its `type` is set to `add` or `remove`),\nor `null` before the first click. Use `evt.xy` for the pixel and\n`ExtjsUtils.COORDINATE.getLatLong(evt)` for the coordinate.",
        "type": "function() : OpenLayers.Event|null",
        "examples": [
          "var evt = inputs.id['property_pick'].getLastEvent();"
        ],
        "returnDescription": "The last handled click event."
      },
      {
        "key": "getPointPos",
        "description": "Returns the geographic position of the last clicked point (the marker), in the map's desired\nprojection (normally EPSG:4326 — longitude, latitude).",
        "type": "function() : Array.<Number>",
        "examples": [
          "var lonLat = inputs.id['property_pick'].getPointPos(); // [-56.07, -4.04]"
        ],
        "returnDescription": "`[x, y]` — longitude and latitude of the last click."
      },
      {
        "key": "iconCls",
        "description": "Defines the CSS class of the toggle switch icon; replace the default to restyle the switch.\n`labelBefore=true` places the text before the switch, and `inputValue` sets the DOM `value` of the\ncheckbox input (both passed through to the checkbox).",
        "type": "String",
        "examples": [
          "|iconCls=my-toggle-icon|"
        ],
        "default": "cmn-toggle-icon"
      },
      {
        "key": "id",
        "description": "Defines the id of the tool (required). It is the key used in `inputs.id[ID]` and the id of the checkbox\ncomponent (`Ext.getCmp(id)`), so it must be unique in the page.",
        "type": "String",
        "examples": [
          "|id=property_pick|"
        ]
      },
      {
        "key": "inputValue",
        "description": "Defines the DOM `value` attribute of the underlying checkbox input (useful when the tool is inside an\nHTML form). It does not affect what `inputs.id[ID]` holds.",
        "type": "String",
        "examples": [
          "|inputValue=pick|"
        ]
      },
      {
        "key": "label",
        "description": "Defines a text template drawn on the map next to each selected feature. `${attr}` placeholders are\nreplaced by the attribute values of the clicked feature (vector layers only; a raster cell has no\nattributes). Without it no label is drawn.",
        "type": "String",
        "examples": [
          "|label=Region ${Name}|"
        ]
      },
      {
        "key": "labelBefore",
        "description": "Set true to render the `text` label before (left of) the toggle switch instead of after it.",
        "type": "Boolean",
        "examples": [
          "|labelBefore=true|"
        ],
        "default": "false"
      },
      {
        "key": "lat",
        "description": "Defines the initial latitude (EPSG:4326, decimal degrees) of the marker shown when the tool is\nactivated, before any click. Use it with `lon`; default 0.",
        "type": "Number",
        "examples": [
          "|lat=-4.0396|lon=-56.07422|"
        ],
        "default": "0"
      },
      {
        "key": "lon",
        "description": "Defines the initial longitude (EPSG:4326, decimal degrees) of the marker shown when the tool is\nactivated, before any click. Use it with `lat` to start on a point of interest; default 0.",
        "type": "Number",
        "examples": [
          "|lat=-4.0396|lon=-56.07422|"
        ],
        "default": "0"
      },
      {
        "key": "markLayerInd",
        "description": "Defines by layer index which one to draw its geometry when a click event happens. (0-indexed)",
        "type": "Number",
        "examples": [
          "|markLayerInd = 0|"
        ]
      },
      {
        "key": "movePoint",
        "description": "Moves the marker of the last click to the position of a map mouse event. The tool calls it on every\nclick; call it yourself only to relocate the marker from a synthetic event.\n@param evt {OpenLayers.Event} Map mouse event carrying the pixel position in `evt.xy`.",
        "type": "function(evt)",
        "examples": [
          "this.getInputs().id['property_pick'].movePoint(evt);"
        ]
      },
      {
        "key": "notify",
        "description": "Set false to prevent the notify messages from appear (the \"click on the map...\" notification\nshown when the tool is activated).",
        "type": "Boolean",
        "examples": [
          "|notify=false|"
        ],
        "default": "true"
      },
      {
        "key": "onMark",
        "description": "Callback function called after clicking on a feature with this 'pickpoint' widget, once the\nfeature info of every layer of the composed map has been received. `this` is the pickpoint\ncheckbox (or `scope`), so `this.value` is the PointAttributeManager. The name is resolved as a\nkey of the layer `functions` object, then as a global function, then as inline function text.\n@param eventAndProperties {Object} A object with {mouse, type, features} passed into the callback function:\n     - mouse: {PointerEvent} The mouse click event.\n     - type: {String} 'add' when the click selected features, 'remove' when it unselected them.\n     - features: {Array<Array<Object>>} One array per layer of the composed map with the feature(s) found at the click (empty on removal).",
        "type": "function",
        "examples": [
          "{ ...\n \"descriptionHtml\": \"{{pickpoint|id=ANY_UNIQUE_ID|onMark=onMarkCallback}}\",\n  functions: {\n    onMarkCallback: function(event) {\n      for (var iLayer=0; iLayer < event.features.length; iLayer++) {\n        for (var iFeature=0; iFeature < event.features[iLayer].length; iFeature++) {\n          console.log(event.features[iLayer][iFeature].data);\n        }\n      }\n    }\n  }\n ...\n }"
        ]
      },
      {
        "key": "onefeature",
        "description": "Defines if PickPoint should keep only the last feature selected.\nSet it true to keep only the last one, false otherwise.",
        "type": "boolean",
        "examples": [
          "|onefeature = false|"
        ]
      },
      {
        "key": "pointVisibility",
        "description": "Defines if Pickpoint should show where the last click was.\nSet it true to show, false otherwise.",
        "type": "Boolean",
        "examples": [
          "|pointVisibility = false|"
        ],
        "default": "true"
      },
      {
        "key": "removeAll",
        "description": "Clears the whole selection: every selected feature is removed from the map and from the list\nreturned by `getAttributes`. Typical use: a \"Clear\" button handler, followed by a recalculation.",
        "type": "function()",
        "examples": [
          "functions: {\n    clearSelection: function() {\n        this.getInputs().id['property_pick'].removeAll();\n    }\n}"
        ]
      },
      {
        "key": "removeAttribute",
        "description": "Removes one selection (by its position in the `getAttributes` list) from the map and from the\nselection list. An invalid index changes nothing.\n@param index {Number} 0-based index of the selection to remove (same order as `getAttributes`).",
        "type": "function(index)",
        "examples": [
          "var pick = inputs.id['property_pick'];\nfor (var i = pick.getAttributes(1).length - 1; i >= 0; i--) {\n    if (!pick.getAttributes(1)[i].Name) pick.removeAttribute(i);\n}"
        ]
      },
      {
        "key": "runOnClick",
        "description": "Alias of `onMark`, kept so the pickpoint accepts the same callback name as the other map tools\n(hoverpixel, summedarea, areaintegral). When both are written, `onMark` wins. See\n{@link PickPoint.onMark} for the callback signature.",
        "type": "function",
        "examples": [
          "|runOnClick=onMarkCallback|"
        ]
      },
      {
        "key": "runOnHover",
        "description": "Defines a callback function to run when clicking at the map.\n\nDefines a callBack with following parameters function(mouseEvt, coordinates)\n--mouseEvt: {MouseEvent} Mouse hover event.\n--coordinates: {Openlayers.LatLon} Coordinate of the cursor over the map.\nPS: Can access the layer itself using the 'this' keyword.",
        "type": "function"
      },
      {
        "key": "runOnHoverOutside",
        "description": "True to run the callback function even when hovering outside of the layer, False to disable. (Default False)",
        "type": "function"
      },
      {
        "key": "scope",
        "description": "Defines the object used as `this` inside the `onMark`/`runOnClick` callback. By default it is the\npickpoint checkbox component (so `this.value` is the PointAttributeManager). From the markup it can\nonly reference an element created earlier in the same description, with the nested `getid` form.",
        "type": "Object",
        "examples": [
          "|scope=getid=report_btn|"
        ]
      },
      {
        "key": "searchAttribute",
        "description": "Finds, among the selected features of one layer, the first whose attribute `propName` equals `value`\nand returns its attributes. Useful to check whether a given feature is already selected.\n@param mapIndex {Number} 0-based index of the layer inside the composed layer.\n@param propName {String} Name of the attribute to compare.\n@param value {*} Value the attribute must have (compared with `==`).",
        "type": "function(mapIndex, propName, value) : Object|null",
        "examples": [
          "var found = inputs.id['property_pick'].searchAttribute(1, 'Code', '3106200');"
        ],
        "returnDescription": "The attributes of the matching selection, or `null` when none matches."
      },
      {
        "key": "setFeatureVisibility",
        "description": "Adds a feature to, or removes it from, the auxiliary vector layer where the tool draws its selections.\nLets a callback draw extra geometries (an `OpenLayers.Feature.Vector`) with the selection, or hide\none of the selected features without forgetting it.\n@param state {Boolean} `true` to add (show) the feature, `false` to remove it.\n@param feature {OpenLayers.Feature.Vector|Array.<OpenLayers.Feature.Vector>} Feature(s) to add or remove.",
        "type": "function(state, feature)",
        "examples": [
          "inputs.id['property_pick'].setFeatureVisibility(true, new OpenLayers.Feature.Vector(geometry));"
        ]
      },
      {
        "key": "setPointVisibility",
        "description": "Shows or hides the marker drawn at the last clicked position (the same marker controlled by the\n`pointVisibility` parameter).\n@param state {Boolean} `true` to show the marker, `false` to hide it.",
        "type": "function(state)",
        "examples": [
          "inputs.id['property_pick'].setPointVisibility(false);"
        ]
      },
      {
        "key": "text",
        "description": "Defines the text shown next to the toggle. When omitted the label shows the coordinate of the last\nclick, `(lat, lon)`, and is updated at every click.",
        "type": "String",
        "examples": [
          "|text=Click on the map to select an area|"
        ]
      },
      {
        "key": "toggle",
        "description": "Activates or deactivates the pick mode from code (same as clicking the switch): the map cursor,\nthe click listeners and the marker follow the new state. Call it on the checkbox component\n(`Ext.getCmp(id)`). Without an argument the state is inverted.\n@param forceState {Boolean} `true` to activate picking, `false` to deactivate; omit to invert.",
        "type": "function(forceState)",
        "examples": [
          "Ext.getCmp('property_pick').toggle(true);"
        ]
      },
      {
        "key": "togglePoint",
        "description": "Adds a set of features (one array per layer of the composed map, as returned by GetFeatureInfo) to\nthe selection, or removes it when the same features were already selected. With `onefeature=true`\nthe previous selection is cleared first. The tool calls it after every click; the returned object is\nwhat `onMark` receives.\n@param pointFeatures {Array.<Array.<OpenLayers.Feature.Vector>>} Features found at the click, indexed by layer.\n@param evt {OpenLayers.Event} The click event that originated the selection.",
        "type": "function(pointFeatures, evt) : Object",
        "returnDescription": "`{mouse, type, features}` — the event, `'add'` or `'remove'`, and the features added (empty on removal)."
      },
      {
        "key": "unselect",
        "description": "Defines if Pickpoint will be automatically disabled after each click.\nSet it true to automatically disable, false otherwise (write `unselect=` or `unselect=false` to\nkeep the tool active for several clicks).",
        "type": "Boolean",
        "examples": [
          "|unselect = true|"
        ],
        "default": "true"
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[ID]`: the PointAttributeManager of the tool, the object that keeps the\nselected features. Read it with `getAttributes(mapIndex)`, `searchAttribute(...)`, `getPointPos()`,\n`getLastEvent()`, and change it with `removeAll()`, `removeAttribute(index)`, `setPointVisibility(state)`,\n`setFeatureVisibility(state, feature)` (all listed in this group). The layer recalculates on the\nwidget `onmark` event, fired after every click once the feature info of all layers arrived — so\n`beforeCalc` always sees the updated selection. The object holds map features, so do not send it to\n`expression` (WebWorker); extract plain values in `beforeCalc`.",
        "type": "Object",
        "examples": [
          "{{pickpoint|id=property_pick|text=Select a property|markLayerInd=1|unselect=}}",
          "beforeCalc: function(inputs) {\n    var codes = inputs.id['property_pick'].getAttributes(1).map(function(a) { return a.Code; });\n    this.changeLayers({name: 'CSR:properties', styles: codes.length ? 'highlight' : '', index: 1});\n}"
        ]
      }
    ],
    "Slider": [
      {
        "key": "_information",
        "completeAPI": "View the complete Slider <a href='/api/#category_Slider'>API here</a>.",
        "description": "Tool that allows users to create a slider that the user can drag and change its value as map input.",
        "name": "slider · number or range (input)"
      },
      {
        "key": "backgroundColors",
        "description": "Array of colors of background slider values, to define background slider color based in slider value.\nThe values are defined from most to minimum with two properties each:\n  - color: {String} CSS color definition for the current interval. i.e. 'red','black','#FF0000', '#000000'.\n  - startValue: {Numeric} If defined define the initial value which above will apply this color, otherwise use theminimum slider value as default.\n\nEx.:\n backgroundColors: [\n     {\n         // Define background color to red when slider has value above 75.\n         color: \"red\",\n         startValue: 75\n     },\n     {\n         // Define background color starting from value 0.\n         // That results two intervals, from 0 to 75 as blue, and from 76 to 100 as red.\n         color: \"blue\",\n         startValue: 0\n     }\n ]",
        "type": "Array.<String>"
      },
      {
        "key": "cls",
        "description": "Extra CSS class(es) added to the slider element (appended to the default `clickable`).",
        "type": "String",
        "examples": [
          "|cls=my_slider|"
        ]
      },
      {
        "key": "disabled",
        "description": "Set true to render the slider disabled (Ext `disabled`); enable it later with `Ext.getCmp(id).enable()`.",
        "type": "Boolean",
        "examples": [
          "|disabled=true|"
        ],
        "default": "false"
      },
      {
        "key": "fieldLabel",
        "description": "Defines the label shown at the left of the slider (Ext `fieldLabel`).",
        "type": "String",
        "examples": [
          "|fieldLabel=Deforestation (%)|"
        ]
      },
      {
        "key": "getValue",
        "description": "Returns the current value of a single-thumb slider. Call it on the component (`Ext.getCmp(id)` or a\n`getid=` reference), not on `inputs.id[ID]`, which already holds the plain value.",
        "type": "function() : Number",
        "examples": [
          "var v = Ext.getCmp('deforestation_slider').getValue();"
        ],
        "returnDescription": "The current slider value."
      },
      {
        "key": "getValues",
        "description": "Returns the value of every thumb; use it for range sliders created with `values=[lo, hi]`.",
        "type": "function() : Array.<Number>",
        "examples": [
          "var range = Ext.getCmp('interval_slider').getValues(); // [lo, hi]"
        ],
        "returnDescription": "One value per thumb, in thumb order."
      },
      {
        "key": "gradient",
        "description": "Set true to blend the `backgroundColors` into a continuous gradient along the filled part of the slider\n(each colour fading into the next from its `startValue`). With the default `false` each interval is a\nsolid colour, with a short blend only around the thumb. Has no effect without `backgroundColors`.",
        "type": "Boolean",
        "examples": [
          "|backgroundColors=[{color: \"green\", startValue: 0}, {color: \"red\", startValue: 50}]|gradient=true|"
        ],
        "default": "false"
      },
      {
        "key": "hideLabel",
        "description": "Set true to hide the label and the space reserved for it (Ext `hideLabel`).",
        "type": "Boolean",
        "examples": [
          "|hideLabel=true|"
        ],
        "default": "false"
      },
      {
        "key": "id",
        "description": "Defines the id to identify the object.",
        "type": "String",
        "examples": [
          "|id=example_slider|"
        ]
      },
      {
        "key": "increment",
        "description": "Defines the step of each increment or decrement in the actual value of the slider when being dragged.",
        "type": "Number",
        "examples": [
          "|increment = 10|"
        ]
      },
      {
        "key": "maxValue",
        "description": "Defines the maximum value of the slider.",
        "type": "Number",
        "examples": [
          "|maxValue = 100|"
        ]
      },
      {
        "key": "minValue",
        "description": "Defines the minimum value of the slider.",
        "type": "Number",
        "examples": [
          "|minValue = 0|"
        ]
      },
      {
        "key": "setValue",
        "description": "Sets the slider value from code. Firing the `change` event (the default) also updates `inputs.id[ID]` and\nrecalculates the layer. For a range slider pass the thumb index first: `setValue(index, value)`.\n@param value {Number} New value (clamped to `minValue`/`maxValue`).\n@param animate {Boolean} Set false to move the thumb without animation.",
        "type": "function(value, animate)",
        "examples": [
          "Ext.getCmp('deforestation_slider').setValue(20);"
        ]
      },
      {
        "key": "thumbStyle",
        "description": "Defines extra CSS class(es) added to the slider thumb (the draggable handle), to restyle it.",
        "type": "String",
        "examples": [
          "|thumbStyle=x-slider-thumb-cut|"
        ],
        "default": "null"
      },
      {
        "key": "value",
        "description": "Defines the initial value of the slider (default 100).\n\nAt runtime the same value is what `inputs.id[ID]` (and `inputs[i]`) holds in `beforeCalc`/`expression`:\na number, or an array `[lower, upper]` when the `values` range form is used. The layer recalculates on\nthe slider `change` event (thumb released or value set from code).",
        "type": "Number",
        "examples": [
          "|value = 100|",
          "beforeCalc: function(inputs) {\n    var threshold = inputs.id['deforestation_slider']; // number\n}"
        ]
      },
      {
        "key": "values",
        "description": "Defines the slider interval limits.\nIf defined, the slider will be displayed as a range slider.\nIt's return at the 'inputs' parameter will be a array of two values.",
        "type": "Array.<Number>",
        "examples": [
          "|values = [0, 250]|",
          "|beforeCalc: function(inputs) {\n   let lowerValue = inputs[0][0]; // The value of the left drag\n   let upperValue = inputs[0][1]; // The value of the right drag\n}|"
        ]
      },
      {
        "key": "width",
        "description": "Defines the slider width in pixels. Any other `Ext.slider.SingleSlider` config (`cls`, `fieldLabel`,\n`hideLabel`, `disabled`, `style`, `keyIncrement`...) is also passed through unchanged.",
        "type": "Number",
        "examples": [
          "|width=200|"
        ]
      }
    ],
    "SummedArea": [
      {
        "key": "_information",
        "completeAPI": "View the complete SummedArea <a href='/api/#category_SummedArea'>API here</a>.",
        "description": "Create an input of summation in any arbitrary area.\n\nCallBack parameters (context {OpenLayers.Layer}, layersValues {Array[Number]}, inputs {[Object]}, feature {Geometry})\nrunOnClick {Function}  Callback called exactly after sum the map region.,\n\nPS: 'layersValues' return the sum of each internal layer and an adittional value which is the sum of all pixels of resulting layer.",
        "name": "summedarea · sum inside a drawn area (input)"
      },
      {
        "key": "description",
        "description": "Creates an input of summatory in any arbitrary area.\n\nCallBack parameters (context {OpenLayers.Layer}, layersValues {Array[Number]}, inputs {[Object]}, feature {Geometry})\nrunOnClick {Function} Callback called exactly after sum the map region.,\n\nPS: 'layersValues' return the sum of each internal layer and an adittional value which is the sum of all pixels of resulting layer.\n@param name {String} Input name \"summedarea\".\n@param config {Object} Properties to customize the element.\n@param layer {OpenLayers.Layer} Layer which the widget is defined.\n@param parameters {Array} Array de parametros antes do processamento para o input.",
        "type": "function(name, config, layer, parameters) : Ext.Container",
        "returnDescription": "Returns an instance of summedarea Input."
      },
      {
        "key": "getSummedLayerValues",
        "description": "Sums, for each inner layer of the composed map, the values of the rendered cells that fall inside a\npolygon (values decoded from the tile colours through the layer legend, null cells skipped). This is\nwhat `runOnClick` receives as `layersValues`; call it from code\n(`Ext.getCmp(id).items.get(0).getSummedLayerValues(layer, feature)`) to sum any polygon feature.\nThe returned array has one sum per inner layer followed by two extra entries: the number of cells\nread from the last layer and an array with the count of non-null cells of every layer.\n@param layer {OpenLayers.Layer.Composed} The composed layer whose tiles are read (normally the tool's own layer).\n@param feature {OpenLayers.Feature.Vector} Polygon feature delimiting the area to sum.",
        "type": "function(layer, feature) : Array",
        "returnDescription": "`[sum layer 0, ..., sum layer n-1, cellCount, [nonNull layer 0, ..., nonNull layer n-1]]`."
      },
      {
        "key": "iconCls",
        "description": "Defines the CSS class of the toggle switch icon; replace the default to restyle the switch.",
        "type": "String",
        "examples": [
          "|iconCls=my-toggle-icon|"
        ],
        "default": "cmn-toggle-icon"
      },
      {
        "key": "id",
        "description": "Defines the id of the tool: the key used in `inputs.id[ID]` and the id of the container component\n(`Ext.getCmp(id)`; the switch itself is `Ext.getCmp(id).items.get(0)`). Generated when omitted.",
        "type": "String",
        "examples": [
          "|id=sum_tool|"
        ]
      },
      {
        "key": "labelBefore",
        "description": "Set true to render the `text` label before (left of) the toggle switch instead of after it.",
        "type": "Boolean",
        "examples": [
          "|labelBefore=true|"
        ],
        "default": "false"
      },
      {
        "key": "notify",
        "description": "Set false to suppress the notification shown when the tool is activated.",
        "type": "Boolean",
        "examples": [
          "|notify=false|"
        ],
        "default": "true"
      },
      {
        "key": "runOnClick",
        "description": "Defines the callback run when the user finishes drawing the polygon (double-click closes it), with the\nvalues already summed. Called as `runOnClick(layersValues, inputs, feature)` with `this` = the layer:\n`layersValues` is the array described in `getSummedLayerValues`, `inputs` is `layer.getInputs()` and\n`feature` the drawn `OpenLayers.Feature.Vector` polygon. The name is resolved as a key of the layer\n`functions` object, then as a global function, then as inline function text. The tool deactivates\nitself after the callback.",
        "type": "function",
        "examples": [
          "|runOnClick=onClickSum|",
          "functions: {\n    onClickSum: function(layersValues, inputs, feature) {\n        ExtjsUtils.ALERTIFY.log('Sum of layer 0: ' + layersValues[0] + ' (' + layersValues[layersValues.length - 1][0] + ' cells)');\n    }\n}"
        ]
      },
      {
        "key": "text",
        "description": "Defines the text shown next to the toggle switch.",
        "type": "String",
        "examples": [
          "|text=Sum an area|"
        ]
      },
      {
        "key": "unselect",
        "description": "Only changes the activation message (`true`: one selection expected, `false`: several); the tool\nalways deactivates itself after a polygon is summed.",
        "type": "Boolean",
        "examples": [
          "|unselect=false|"
        ],
        "default": "true"
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[ID]`: an array meant to hold the last `layersValues` (one sum per inner\nlayer, see `getSummedLayerValues`). In the current implementation the tool does not copy the sums into\nit after drawing — it stays empty — so read the results in `runOnClick` and store what you need with an\nInputManager (`setValues`). The input is registered on the `forceupdatelayer` event of the switch\n(`Ext.getCmp(id).items.get(0).forceUpdateLayer()` recalculates the layer).",
        "type": "Array.<Number>",
        "examples": [
          "{{summedarea|id=sum_tool|runOnClick=onClickSum}}{{inputmanager|id=sums}}",
          "functions: {\n    onClickSum: function(layersValues, inputs, feature) {\n        inputs.id['sums'].setValues({areaSum: layersValues[0]}); // recalculates the layer\n    }\n}"
        ]
      }
    ],
    "Textfield": [
      {
        "key": "_information",
        "completeAPI": "This tool is created from <a href='https://docs.sencha.com/extjs/3.4.0/#!/api/Ext.form.TextField' target='_blank'>Ext.form,TextField</a>.\nOnly customized properties are <a href='/api/#category_Textfield'>listed here</a>.",
        "description": "Tool that allows the user to input a single line of text.\nUsage: '{{textfield}}'",
        "name": "textfield · text box (input)"
      },
      {
        "key": "afteredit",
        "description": "Event fired once the field loses focus (blur) and its text differs from the value it had when editing\nstarted; the listener receives the TextField. Unlike `keyup` it fires a single time per edit, so use\nit for expensive reactions. Attach it with `on_afteredit=` through a `getid=` reference, or from code\nwith `Ext.getCmp(id).on('afteredit', fn)`.",
        "type": "Event",
        "examples": [
          "{{textfield|id=soy_value|isnumeric}}{{label|getid=soy_value|getid=on_afteredit=console.log(this.getRawValue())}}",
          "Ext.getCmp('soy_value').on('afteredit', function(field) { console.log(field.getRawValue()); });"
        ]
      },
      {
        "key": "fieldLabel",
        "description": "Defines the TextField label.",
        "type": "String",
        "examples": [
          "|fieldLabel = This is the field label|"
        ]
      },
      {
        "key": "getRawValue",
        "description": "Returns the text currently in the field, unprocessed (this is what `inputs.id[ID]` holds). Call it on the\ncomponent (`Ext.getCmp(id)`).",
        "type": "function() : String",
        "examples": [
          "var text = Ext.getCmp('soy_value').getRawValue();"
        ],
        "returnDescription": "The raw text of the input element."
      },
      {
        "key": "hideLabel",
        "description": "Defines if it should completely hide the label element (label and separator) of the TextField.\nThat is, if this property is set to true, the label will be hidden. Otherwise, the label will be shown by default.\nPS: Since the label will be shown by default, even if you do not specify a fieldLabel, the space for it will still be reserved so that the TextField will line up with other fields that do have labels. This space will be removed if you define it to be hidden.",
        "type": "Boolean",
        "examples": [
          "|hideLabel = true|"
        ],
        "default": "false"
      },
      {
        "key": "id",
        "description": "Defines the id to identify the object.",
        "type": "String",
        "examples": [
          "|id=example_text_field|"
        ]
      },
      {
        "key": "isnumeric",
        "description": "Defines the TextField content as numeric only.",
        "type": "boolean",
        "examples": [
          "|isnumeric=true|"
        ]
      },
      {
        "key": "setRawValue",
        "description": "Replaces the text of the field from code. It does not fire `keyup`, so call `forceRecalc()` on an\nInputManager (or fire the event) when the layer must be recalculated with the new text.\n@param value {String} New text for the field.",
        "type": "function(value)",
        "examples": [
          "Ext.getCmp('soy_value').setRawValue('43');"
        ]
      },
      {
        "key": "style",
        "description": "Defines the TextField style properties.\nYou can use CSS style rules to customize it.",
        "type": "String",
        "examples": [
          "|style = color:black;|"
        ]
      },
      {
        "key": "value",
        "description": "Defines the initial value to the TextField content.\n\nAt runtime `inputs.id[ID]` (and `inputs[i]`) holds the current text exactly as typed (`getRawValue()`,\nalways a string — convert it with `parseFloat` when `isnumeric` is used). The layer recalculates on\nevery `keyup` (each keystroke); the widget also fires `afteredit` when the field loses focus with a\nchanged value.",
        "type": "String",
        "examples": [
          "|value = text|",
          "beforeCalc: function(inputs) {\n    var price = parseFloat(inputs.id['soy_value']) || 0;\n}"
        ]
      }
    ],
    "Timeline": [
      {
        "key": "_information",
        "examples": [
          "{{timeline|nextStepInterval=1000|id=ANY_UNIQUE_ID|steps=[[“01”, “January”], [“02”, “February”], [“March”], [“April”]]}}"
        ],
        "completeAPI": "View the complete Timeline <a href='/api/#category_Timeline'>API here</a>.",
        "description": "Display a spatial scenarios changes in a timeline.",
        "name": "timeline · scenarios over time (input)"
      },
      {
        "key": "fieldLabel",
        "description": "Defines a label for the Timeline.\nIt will be shown at the left of the button to hide the tiemline, by default.",
        "type": "String",
        "examples": [
          "|fieldLabel=This is the field label|"
        ]
      },
      {
        "key": "getCurrentLayerStyle",
        "description": "Returns the WMS style currently applied to the layer (`layer.params.STYLES`), or an empty string for\nthe default style.",
        "type": "function() : String",
        "examples": [
          "var style = Ext.getCmp('years_tl').getTimeline().getCurrentLayerStyle();"
        ],
        "returnDescription": "The active style name."
      },
      {
        "key": "getDesiredVisibility",
        "description": "Returns the visibility the timeline has, or will have when its layer becomes visible: `true` may be\nreturned while the panel is actually hidden because the layer is hidden.",
        "type": "function() : Boolean",
        "examples": [
          "var shown = Ext.getCmp('years_tl').getTimeline().getDesiredVisibility();"
        ],
        "returnDescription": "`true` when the timeline is (or will be) shown together with the layer."
      },
      {
        "key": "getMainThumb",
        "description": "Returns the main (middle) thumb of the slider — the one that selects the current step; its `value` is\nthe 0-based step index. The outer thumbs (`thumbs[0]`, `thumbs[2]`) bound the animation range.",
        "type": "function() : Ext.slider.Thumb",
        "examples": [
          "var stepIndex = Ext.getCmp('years_tl').getTimeline().getMainThumb().value;"
        ],
        "returnDescription": "The main thumb; read the step index from `.value`."
      },
      {
        "key": "getMaxThumbValue",
        "description": "Returns the position of the right (max) thumb: the last step index the animation reaches.",
        "type": "function() : Number",
        "examples": [
          "var last = Ext.getCmp('years_tl').getTimeline().getMaxThumbValue();"
        ],
        "returnDescription": "0-based step index of the max thumb."
      },
      {
        "key": "getMinThumbValue",
        "description": "Returns the position of the left (min) thumb: the step index the animation restarts from.",
        "type": "function() : Number",
        "examples": [
          "var first = Ext.getCmp('years_tl').getTimeline().getMinThumbValue();"
        ],
        "returnDescription": "0-based step index of the min thumb."
      },
      {
        "key": "getStyleFromValue",
        "description": "Returns the style of a step key or, when the key has no style of its own, the style of the nearest\nprevious key (numeric keys are compared as numbers, text keys by position in `steps`).\n@param value {String|Number} A step key.",
        "type": "function(value) : String|Object|undefined",
        "examples": [
          "var style = Ext.getCmp('years_tl').getTimeline().getStyleFromValue('2014');"
        ],
        "returnDescription": "The style (name or `{style, name}` object), or `undefined` when no previous step has one."
      },
      {
        "key": "getTimeline",
        "description": "Returns the timeline panel driven by this button. The markup `id` identifies the button, so this is the\nway to reach the panel methods (`getValue`, `updateMainThumbValue`, `startAnimationStep`, `setSteps`...).",
        "type": "function() : GeoExt.TimelinePanel",
        "examples": [
          "var timeline = Ext.getCmp('years_tl').getTimeline();\ntimeline.updateMainThumbValue(0); // first step"
        ],
        "returnDescription": "The timeline panel (null after the button was destroyed)."
      },
      {
        "key": "getValue",
        "description": "Returns the key of the step currently selected by the main thumb (the same value the layer receives in\n`inputs.id[ID]`).",
        "type": "function() : String",
        "examples": [
          "var year = Ext.getCmp('years_tl').getTimeline().getValue(); // '2010'"
        ],
        "returnDescription": "The current step key (first element of the `steps` entry)."
      },
      {
        "key": "getValueFromStyle",
        "description": "Returns the step key whose style is the given one (the last match when several steps share a style),\nor the first step key when the style is empty or unknown. The key is returned through `parseInt`, so it\nis meaningful for numeric keys only (text keys give `NaN`).\n@param style {String} A style name as used in `steps`.",
        "type": "function(style) : Number",
        "examples": [
          "var year = Ext.getCmp('years_tl').getTimeline().getValueFromStyle('style_2010'); // 2010"
        ],
        "returnDescription": "The numeric step key."
      },
      {
        "key": "hidden",
        "description": "Set true to hide the \"Show/Hide timeline\" button rendered in the layer description (the standard Ext\n`hidden` config, so `Ext.getCmp(id).show()` reveals it). The timeline panel itself is still created\nand its initial visibility follows `renderHidden`; use it when the timeline is driven by a\n`paramsButtonConfig` button or by code instead. Any other `Ext.Button` config is passed through.",
        "type": "Boolean",
        "examples": [
          "|hidden=true|"
        ],
        "default": "false"
      },
      {
        "key": "hideLabel",
        "description": "Defines if the label of the timeline should be displayed.\nSet true to hide the label, false to show it.",
        "type": "Boolean",
        "examples": [
          "|hideLabel=true|"
        ],
        "default": "false"
      },
      {
        "key": "id",
        "description": "Defines the id to identify the object. It is the key used in `inputs.id[ID]` and the id of the\nshow/hide button component: `Ext.getCmp(id)` returns the button and `Ext.getCmp(id).getTimeline()`\nthe timeline panel (see the methods listed in this group).",
        "type": "String",
        "examples": [
          "|id=exemple_timeline|"
        ]
      },
      {
        "key": "nextAnimationStep",
        "description": "Advances the timeline one step (main thumb + 1), applying the style of the new step to the layer and\nfiring `change`; does nothing when the main thumb is already at the max thumb. Useful for a custom\n\"next\" button.",
        "type": "function()",
        "examples": [
          "Ext.getCmp('years_tl').getTimeline().nextAnimationStep();"
        ]
      },
      {
        "key": "nextStepInterval",
        "description": "Defines the duration of the interval between steps of the timeline in milliseconds.",
        "type": "Number",
        "examples": [
          "|nextStepInterval=1000|"
        ],
        "default": "2100"
      },
      {
        "key": "onPlayToggle",
        "description": "Defines the callback function called when the play/stop button is toggled, BEFORE the animation starts\nor stops. It must return a truthy value: returning `false`/nothing cancels the start/stop (use it as a\nveto, e.g. while data is loading). `pressed` is true when the animation is about to start.\n\nThe value is resolved as a key of the layer `functions` object, then as a global function with that name,\nthen as inline function text (`function(...){...}`; a plain statement body also works and returns true).\n@param pressed {Boolean} True if the button was pressed (animation about to start), False otherwise.\n@param layer {Object} The layer associated to this timeline.\n@param timeline {Object} The timeline panel.\n@param playBtn {Object} The play/stop button.",
        "type": "function",
        "examples": [
          "|onPlayToggle=onPlayToggle|",
          "|onPlayToggle = function (pressed, layer, timeline, playBtn) {\n     console.log(\"The timeline is about to \" + (pressed ? \"start\" : \"stop\"));\n     return true; // required, a falsy return cancels the toggle\n}|"
        ]
      },
      {
        "key": "playing",
        "description": "`true` while the animation is running (between play and stop/end). Read it on the panel\n(`Ext.getCmp(id).getTimeline().playing`) to know whether to call `startAnimationStep()` or `stopAnimation()`.",
        "type": "Boolean",
        "examples": [
          "if (!Ext.getCmp('years_tl').getTimeline().playing) Ext.getCmp('years_tl').getTimeline().startAnimationStep();"
        ],
        "default": "false"
      },
      {
        "key": "preloadTiles",
        "description": "Defines if the timeline should preload the tiles of the next steps to get smoother transitions.\nSet it true to preload, false otherwise.",
        "type": "Boolean",
        "examples": [
          "|preloadTiles = true|"
        ],
        "default": "false"
      },
      {
        "key": "renderHidden",
        "description": "Defines the timeline initial visibility.\nSet it true to start with the timeline hidden, false otherwise.",
        "type": "Boolean",
        "examples": [
          "|renderHidden = true|"
        ],
        "default": "false"
      },
      {
        "key": "setCurrentLayerStyle",
        "description": "Applies a style to the layer the way a step does: a style name, or a `{style, name}` object that also\nswitches the WMS layer name (the object form of a `steps` entry). For a composed layer the first inner\nlayer is updated and the legend is rebuilt. The slider is not moved (use `updateMainThumbValue`).\n@param style {String|Object} Style name (`''` for the default) or `{style: 'name', name: 'CSR:layer'}`.",
        "type": "function(style)",
        "examples": [
          "Ext.getCmp('years_tl').getTimeline().setCurrentLayerStyle({style: 'estados_2', name: 'CSR:estados'});"
        ]
      },
      {
        "key": "setDesiredVisibility",
        "description": "Shows or hides the timeline panel while keeping it consistent with the layer: when the layer is hidden the\npanel stays hidden and the requested state is remembered, to be applied as soon as the layer becomes\nvisible. Prefer it over `show()`/`hide()`; the show/hide button follows the panel automatically.\n@param visible {Boolean} `true` to show the timeline (once the layer is visible), `false` to hide it.",
        "type": "function(visible) : GeoExt.TimelinePanel",
        "examples": [
          "Ext.getCmp('years_tl').getTimeline().setDesiredVisibility(true);"
        ],
        "returnDescription": "The panel, for chaining."
      },
      {
        "key": "setMaxThumbValue",
        "description": "Moves the right (max) thumb, limiting the animation to the steps up to that index (no animation, no\n`change` event).\n@param value {Number} 0-based step index for the max thumb.",
        "type": "function(value)",
        "examples": [
          "Ext.getCmp('years_tl').getTimeline().setMaxThumbValue(5);"
        ]
      },
      {
        "key": "setMinThumbValue",
        "description": "Moves the left (min) thumb, making the animation start from that step index (no animation, no\n`change` event).\n@param value {Number} 0-based step index for the min thumb.",
        "type": "function(value)",
        "examples": [
          "Ext.getCmp('years_tl').getTimeline().setMinThumbValue(2);"
        ]
      },
      {
        "key": "setSteps",
        "description": "Replaces the steps of the timeline and redraws it (labels, slider range and current step). Unlike the\nmarkup `steps` parameter this takes the already-built object: keys are the step labels, values the\nstyle name or a `{style, name}` object. Use it to change the available periods at runtime (e.g. after\na combobox selection).\n@param steps {Object} Map of step key to style: `{'1990': 'style_1990', '2000': {style: 's2000', name: 'CSR:layer'}}`.",
        "type": "function(steps)",
        "examples": [
          "Ext.getCmp('years_tl').getTimeline().setSteps({2000: 'style_2000', 2010: 'style_2010'});"
        ]
      },
      {
        "key": "startAnimationStep",
        "description": "Starts (or resumes) the animation from the current step: each step is shown for `nextStepInterval`\nmilliseconds after the layer finished loading it, up to the max thumb, where the animation stops by\nitself. When the main thumb is already at the max thumb it restarts from the min thumb. Same as pressing\nthe play button, except that `onPlayToggle` is not consulted.",
        "type": "function()",
        "examples": [
          "Ext.getCmp('years_tl').getTimeline().startAnimationStep();"
        ]
      },
      {
        "key": "steps",
        "description": "Defines the timeline change steps.",
        "type": "Array.<Array.<(String|Object)>>",
        "examples": [
          "|steps=[[\"step_0\"], [\"step_1\"], [\"step_2\"]]|",
          "|steps=[['Nome', {style:\"step_0_style\",name:\"CSR:estados\"}], ['Região', {style:\"step_1_style\",name:\"CSR:estados\"}], ['Geocódigo', {style:\"step_2_style\",name:\"CSR:estados\"}]]|",
          "|steps=[['Nome', 'step_0'], ['Região', 'step_1'], ['Geocódigo', 'step_2']]|",
          "|steps=[{1990: \"layer_style0\", 1991: \"layer_style1\", 1992: \"layer_style2\"}, 1993: \"layer_style3\"}]|"
        ]
      },
      {
        "key": "stopAnimation",
        "description": "Stops the running animation (cancels the pending step timer, releases the play button and hides the\nslider tip). The current step is kept. Safe to call when nothing is playing.",
        "type": "function()",
        "examples": [
          "Ext.getCmp('years_tl').getTimeline().stopAnimation();"
        ]
      },
      {
        "key": "toggleTimelineVisibility",
        "description": "Shows or hides the timeline panel from code — what clicking the button does. Without an argument the\ncurrent (desired) visibility is inverted. The panel only appears while the layer is visible; the\nrequested state is remembered otherwise (see `setDesiredVisibility`).\n@param forceState {Boolean} `true` to show the timeline, `false` to hide it; omit to invert.",
        "type": "function(forceState)",
        "examples": [
          "Ext.getCmp('years_tl').toggleTimelineVisibility(true);"
        ]
      },
      {
        "key": "updateLayer",
        "description": "Applies to the layer the style of a step key, without moving the slider. When the key has no step of its\nown the style of the nearest previous numeric key is used (steps `{2010: \"s1\", 2015: \"s2\"}` and value\n2014 keep/apply \"s1\"); nothing happens when that style is already active. Called by the slider on every\nchange; call it yourself to preview a step, then `updateMainThumbValue()` to sync the thumb.\n@param value {String|Number} A step key (first element of a `steps` entry).",
        "type": "function(value)",
        "examples": [
          "Ext.getCmp('years_tl').getTimeline().updateLayer('2010');"
        ]
      },
      {
        "key": "updateMainThumbValue",
        "description": "Moves the main thumb to a step index (0-based position in `steps`), applying that step's style to the\nlayer and firing `change` — the programmatic way to select a step. Without an argument it re-syncs the\nthumb with the layer's current style using `getValueFromStyle` (used when another tool changes the\nstyle). The min/max thumbs are pushed outwards when the index falls outside the current range.\n@param value {Number} Step index; omit to sync the thumb with the layer's current style.",
        "type": "function(value)",
        "examples": [
          "Ext.getCmp('years_tl').getTimeline().updateMainThumbValue(2); // third step"
        ]
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[ID]` (and `inputs[i]`): the key of the current step — the first element of\nthe selected `steps` entry (e.g. `\"1990\"` or `\"January\"`), as a string. It changes whenever the main\nthumb moves (drag, click or animation) and the layer recalculates on the widget `change` event, after\nthe layer style of the step was applied. The same `change` event is fired on the button\n(`Ext.getCmp(id).on('change', function(button, value) {...})`).",
        "type": "String",
        "examples": [
          "{{timeline|id=years_tl|nextStepInterval=1500|steps=[['1990', 'style_1990'], ['2000', 'style_2000'], ['2010', 'style_2010']]}}",
          "beforeCalc: function(inputs) {\n    var year = parseInt(inputs.id['years_tl'], 10);\n}"
        ]
      }
    ],
    "Window": [
      {
        "key": "_information",
        "completeAPI": "This tool is created from <a href='https://docs.sencha.com/extjs/3.4.0/#!/api/Ext.Window' target='_blank'>Ext.Window</a>.",
        "description": "Tool that allows to show contents in an interactive floating window.\nIt can only be shown when the layer is visible.",
        "name": "window · floating window (input)",
        "examples": [
          "{{window|id=UNIQUE_ID|startVisible=true|underButtons=true}}"
        ]
      },
      {
        "key": "btnID",
        "description": "Defines the id of the button that controls the window visibility.",
        "type": "String",
        "examples": [
          "|btnID = window-button-id|"
        ]
      },
      {
        "key": "getButton",
        "description": "Returns the show/hide toggle `Ext.Button` (id `btnID`), e.g. to `toggle(true)`, `setText()` or `hide()` it.",
        "type": "function() : Ext.Button",
        "examples": [
          "this.getInputs().id['report_btn'].getButton().setText('Hide report');"
        ],
        "returnDescription": "The toggle button component."
      },
      {
        "key": "getContainer",
        "description": "Returns the content container (`Ext.Container`, id `id`) inside the window. Use `update(html)` to\nreplace its HTML, or `add()`/`doLayout()` to place Ext components in it.",
        "type": "function() : Ext.Container",
        "examples": [
          "this.getInputs().id['report_btn'].getContainer().update('<p>' + text + '</p>');"
        ],
        "returnDescription": "The content container of the window."
      },
      {
        "key": "getIds",
        "description": "Returns the ids of the three components created by the tool, in the order\n`[windowID, id (content div), btnID]`.",
        "type": "function() : Array.<String>",
        "examples": [
          "var ids = this.getInputs().id['report_btn'].getIds(); // ['report_win', 'report_div', 'report_btn']"
        ],
        "returnDescription": "`[windowId, containerId, buttonId]`."
      },
      {
        "key": "getWindow",
        "description": "Returns the floating `Ext.Window` (use it for `show()`, `hide()`, `setTitle()`, `setSize()`,\n`setPosition()`...). Prefer `getButton().toggle()` to change visibility so the button stays in sync.",
        "type": "function() : Ext.Window",
        "examples": [
          "this.getInputs().id['report_btn'].getWindow().setTitle('Report - ' + year);"
        ],
        "returnDescription": "The window component (it is destroyed together with the layer)."
      },
      {
        "key": "height",
        "description": "Defines the floating window initial height.",
        "type": "number",
        "examples": [
          "|height = 600px|"
        ]
      },
      {
        "key": "html",
        "description": "Defines the initial HTML content of the window's container div. Because the parser treats everything\nafter `html=` as the value, the content may contain `=` characters; it cannot contain `|` or `}}`.\nFor content built at runtime, write into the container instead (`inputs.id[ID].getContainer().update(html)`\nor `Ext.getCmp(id).update(html)`).",
        "type": "String",
        "examples": [
          "|html=<div class=\"report\">Loading...</div>|"
        ]
      },
      {
        "key": "id",
        "description": "Defines an id for the div contained in the floating window (where the content can be drawn).",
        "type": "String",
        "examples": [
          "|id = window-div-id|"
        ]
      },
      {
        "key": "ignoreVisibility",
        "description": "Defines if the window should ignore the layer visibility state. \nBy default, the window visibility state is the same as the layer's. \nSet true to ignore the layer visibility state, false otherwise.\n\nPS: The 'ignoreVisibility' is not compatible with the 'associatedButtonID' property. When both are used together, the ignoreVisibility value is ignored.",
        "type": "Boolean",
        "examples": [
          "|ignoreVisibility = false|"
        ]
      },
      {
        "key": "items",
        "description": "Not supported: the tool always creates its own single content container (the div identified by `id`), so\nany `items` written in the markup are discarded. Put content in with `html=`, or render into the\ncontainer from code (`Ext.getCmp(id)` / `inputs.id[ID].getContainer()`).",
        "type": "Array"
      },
      {
        "key": "onBeforeHide",
        "description": "Defines a callback function to be called before hiding the floating window.\n@param layer {Object} Scope of the layer\n@param data {Array} [window, button, windowConfig]\nwindow: Window object;\nbutton: Button created;\nwindowConfig: Window object configuration",
        "type": "function",
        "examples": [
          "|onBeforeHide = function (layer, [window, button, windowConfig]){\n console.log(layer);\n console.log([window, button, windowConfig]);\n}|"
        ]
      },
      {
        "key": "resize",
        "description": "Defines the callback function to be called when the floating window is resized.",
        "type": "function",
        "examples": [
          "|resize = function (){\n console.log(\"I was resized!\");\n}|"
        ]
      },
      {
        "key": "startVisible",
        "description": "Defines if the window should start visible or not. \nSet true if it should, false otherwise.",
        "type": "Boolean",
        "examples": [
          "|startVisible = false|"
        ]
      },
      {
        "key": "text",
        "description": "Defines the window button text.",
        "type": "string",
        "examples": [
          "|text = I am a button|"
        ],
        "default": "Show/Hide Window"
      },
      {
        "key": "title",
        "description": "Defines the floating window's title.",
        "type": "String",
        "examples": [
          "|title = Title of the Window|"
        ],
        "default": "Window"
      },
      {
        "key": "toggle",
        "description": "Shows or hides the floating window from code by pressing/releasing its toggle button — the same as\nthe user clicking it, so `onBeforeHide` still runs. Call it on the button:\n`inputs.id[ID].getButton().toggle(state)` or `Ext.getCmp(btnID).toggle(state)`. Without an argument\nthe state is inverted.\n@param pressed {Boolean} `true` to show the window, `false` to hide it; omit to invert.",
        "type": "function(pressed)",
        "examples": [
          "this.getInputs().id['report_window'].getButton().toggle(true);"
        ]
      },
      {
        "key": "underButtons",
        "description": "Defines where the floating window will be positioned in relation to the buttons panel.\nSet true to position the window under the right buttons panel, false otherwise.",
        "type": "Boolean",
        "examples": [
          "|underButtons = false|"
        ]
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[btnID]` - under the show/hide button's id, not the markup `id`, so set\n`btnID` to read it (without one the key is an automatic id): a small handle object with the methods `getIds()`, `getWindow()`,\n`getButton()` and `getContainer()` to reach the three Ext components of the tool (the floating window,\nits content div and the show/hide button). The input is registered on the window `afterrender` event,\nso it triggers one recalculation when the window is first rendered; it never changes afterwards. The\nhandle contains component references, so do not use it inside `expression` (WebWorker) — read it in\n`beforeCalc` or in button/window callbacks.",
        "type": "Object",
        "examples": [
          "{{window|id=report_div|btnID=report_btn|windowID=report_win|title=Report|startVisible=true}}",
          "beforeCalc: function(inputs) {\n    var wnd = inputs.id['report_btn'];\n    wnd.getContainer().update('<b>Total:</b> ' + total);\n    wnd.getButton().toggle(true);\n}"
        ]
      },
      {
        "key": "width",
        "description": "Defines the floating window initial width.",
        "type": "number",
        "examples": [
          "|width = 600px|"
        ]
      },
      {
        "key": "windowID",
        "description": "Defines the window id.",
        "type": "String",
        "examples": [
          "|windowID = window-id|"
        ]
      },
      {
        "key": "x",
        "description": "Defines the initial absolute X position of the window, in pixels from the left edge of the page.\nA negative value counts from the right edge instead: the window's right side is placed at\n`viewportWidth + x` (e.g. `x=-10` leaves a 10px margin on the right).\n\nPS: Overwritten when 'underButtons' is used (or when neither x nor y is given).",
        "type": "Number",
        "examples": [
          "|x=-115|y=103|"
        ]
      },
      {
        "key": "y",
        "description": "Defines the initial absolute Y position of the window, in pixels from the top of the page.\nA negative value counts from the bottom edge instead: the window's bottom side is placed at\n`viewportHeight + y` (e.g. `y=-20` leaves a 20px margin at the bottom).\n\nPS: Overwritten when 'underButtons' is used (or when neither x nor y is given).",
        "type": "Number",
        "examples": [
          "|x=20|y=-20|"
        ]
      }
    ],
    "ZoomLevel": [
      {
        "key": "_information",
        "description": "A hidden input whose value is the current map zoom level; the layer recalculates whenever the zoom changes.\nUsage: {{zoomlevel|id=zoom}} then inputs.id['zoom'] in beforeCalc/expression",
        "name": "zoomlevel · current zoom (input)"
      },
      {
        "key": "id",
        "description": "Defines the id of the input; it is the key used to read the zoom level in `inputs.id[ID]`\n(required, the tool renders nothing visible).",
        "type": "String",
        "examples": [
          "|id=zoom|"
        ]
      },
      {
        "key": "value",
        "description": "Value stored in `inputs.id[ID]` (and `inputs[i]`) for `beforeCalc`/`expression`: the current map zoom\nlevel as a number. It is refreshed on the map `moveend` event and only triggers a recalculation when\nthe zoom actually changed (panning is ignored).",
        "type": "Number",
        "examples": [
          "{{zoomlevel|id=zoom}}",
          "beforeCalc: function(inputs) {\n    var zoom = inputs.id['zoom'];\n    this.changeLayers({name: zoom > 10 ? 'CSR:municipalities' : 'CSR:estados', index: 0});\n}"
        ]
      }
    ]
  },
  "Query Setup": {
    "_information": {
      "title": "Query setup: calls before the layer list",
      "description": "Calls written before the list of layers and joined to it with &&. They change the whole map - settings, globals, extra map servers, coordinate systems - until another query is applied. Each returns true, so the list after && is still read.\nA query-wide setting and a registered server before the list:",
      "example": "ExtjsUtils.CONFIGURATION.setOptions({ keepOnLeave: false }) &&\nExtjsUtils.QUERY.addRemoteWMSServer({\n  prodes: { url: \"https://terrabrasilis.dpi.inpe.br/geoserver/prodes-legal-amz/ows?\" },\n}) && [\n  { name: \"yearly_deforestation\", source: \"prodes\", title: \"Deforestation (INPE)\", visibility: true },\n];"
    },
    "QUERY": [
      {
        "key": "_information",
        "completeAPI": "View the complete Query <a href='/api/#category_QUERY'>API here</a>.",
        "description": "ExtjsUtils.QUERY: calls before the list (setQueryGlobalProperties, addRemoteWMSServer, decorate, setMappiaIoCallback) and changes to the running query from your functions (addLayer, removeLayer, postMessage).",
        "name": "QUERY · setup calls and the running query"
      },
      {
        "key": "PathDescription",
        "description": "Internal — constructor of the objects the interpreter stores in each layer's `path` and\n`viewPath` arrays to describe the group (or view) the layer belongs to, one entry per\nnesting level. `new PathDescription(groupNode, layerIndex, cfg)`: `groupNode` is the group\ndefinition object (its `title` is used) or a bare title string; `layerIndex` is the position\nof the layer in the flattened definition; `cfg` is copied as the group's configuration.\nInstances expose `getTitle()` (group/view title), `getCfg()` (copy of the group definition)\nand `getDefinitionIndex()` (the layer index). Tenant code normally does not build these —\n`QUERY.addLayer` with a group object does it — but a hand-built layer record can set\n`path: [new ExtjsUtils.QUERY.PathDescription(\"My group\", 0, null)]` to appear under a group\nin the layer tree.\n@param groupNode {Object|String} Group definition object (its `title` is used) or the title itself.\n@param layerIndex {Number} Index of the layer in the flattened query definition.\n@param cfg {Object} Group configuration to copy into the descriptor (may be null).",
        "type": "function",
        "qualifiedName": "ExtjsUtils.QUERY.PathDescription",
        "examples": [
          "var pathEntry = new ExtjsUtils.QUERY.PathDescription(\"Remote layers\", 0, null);\npathEntry.getTitle(); // \"Remote layers\""
        ]
      },
      {
        "key": "addLayer",
        "description": "Adds layers to the map at runtime from the same definitions used in the query array.\n`cfg` may be a single layer definition, an array of definitions, or a full group object\n(`{viewTitle, title, color, elements: [...]}`) whose elements are then shown grouped in the\nlayer tree. The definition goes through the regular query parse pipeline (group defaults,\n`path`/`viewPath`, priority order) with the current query's globals kept, so functions inside\nit (handlers, `styleMap` callbacks) still work. Side-effect APIs such as `addRemoteWMSServer`\nor `decorate` cannot be embedded in a definition object: register remote sources before\ncalling `addLayer`, otherwise a layer whose `source` is unknown is skipped with\n\"Source not found by name\" in the console. Typically called from a `setMappiaIoCallback`\nhandler or a button `handler` to inject layers on demand.\n@param cfg {Object|Array.<Object>} One layer definition, an array of definitions, or a group object with `elements`.",
        "type": "function(cfg) : Array.<GeoExt.data.LayerRecord>",
        "qualifiedName": "ExtjsUtils.QUERY.addLayer",
        "examples": [
          "// Single layer\nExtjsUtils.QUERY.addLayer({ name: \"CSR:estados\", title: \"States\", visibility: true });",
          "// A whole group, shown under its own view/title in the layer tree\nExtjsUtils.QUERY.addLayer({\n  viewTitle: \"Data sources\",\n  title: \"Boundaries\",\n  color: \"#0073E6\",\n  elements: [\n    { name: \"CSR:estados\", title: \"States\", visibility: true },\n    { name: \"CSR:municipios\", title: \"Municipalities\", visibility: false }\n  ]\n});"
        ],
        "returnDescription": "The layer records added to the map (skipped definitions are omitted)."
      },
      {
        "key": "addNewLayerAsBackground",
        "description": "Adds a layer and uses it as the background map: the definition gets `group: \"background\"`,\n`visibility: true` and a very low `priority` (so it stays under every other layer) and is added\nwith `QUERY.addLayer`. By default the current background layers are hidden first. A bare layer\nname string is accepted as the definition.\n@param layerDefinition {Object|String} Layer definition properties, or just the layer name.\n@param hideBackgroundLayers {Boolean} Hide the existing background layers before adding the new one.",
        "type": "function(layerDefinition, hideBackgroundLayers)",
        "qualifiedName": "ExtjsUtils.QUERY.addNewLayerAsBackground",
        "examples": [
          "ExtjsUtils.QUERY.addNewLayerAsBackground({\"name\": \"CSR:paises\"})",
          "ExtjsUtils.QUERY.addNewLayerAsBackground(\"CSR:paises\", false)"
        ]
      },
      {
        "key": "addRemoteWMSServer",
        "description": "Registers extra WMS servers as layer sources for the current query. Each key of\n`wmsDefinitions` becomes a source id: a layer selects it with `source: \"<id>\"`\nand uses the layer name published by that server's capabilities as `name`.\nChain it with `&&` before the layer array so the sources exist when the layers\nare created. Skipped in \"just eval\" mode.\n\nSource definition properties:\n - `url` {String} URL of the WMS (mandatory unless `ptype` is given).\n - `cors` {Boolean} Route the requests through the Mappia CORS proxy (`/cors/`)\n   for servers without CORS headers.\n - `updateWMS` {Boolean} Bypass the proxy cache (`/cors/direct/`) to get fresh content.\n - `storage` {String} Rewrites `url` for hosted layer stores: `\"github\"` (raw\n   GitHub content, cache-busted), `\"jsdelivr\"` (jsDelivr CDN, append `@VERSION`\n   to the URL to force an update; releases: https://help.github.com/en/articles/creating-releases),\n   `\"remote\"` (no rewrite). All storage values append `/getCapabilities.xml?`.\n - `ptype` {String} Source plugin type, default `\"gxp_customwmscsource\"`.\n - `onLoad` {Function} Called when the source capabilities are loaded.\n - `onError` {Function} Called when the source fails to load.\n\nWith `?options=capabilities` (calculator only) every URL is replaced by the\nper-query filtered capabilities endpoint.\n@param wmsDefinitions {Object} Map of source id to source definition.",
        "type": "function(wmsDefinitions) : Boolean",
        "qualifiedName": "ExtjsUtils.QUERY.addRemoteWMSServer",
        "examples": [
          "ExtjsUtils.QUERY.addRemoteWMSServer({\n geoinfo: {\n  url: \"http://geoinfo.cnps.embrapa.br/geoserver/wms?SERVICE=WMS&\",\n  cors: true\n },\n ibge: {\n  url: \"https://geoservicos.ibge.gov.br/geoserver/CCAR/wms?\",\n  cors: true\n },\n gitHub: {\n   url: \"https://github.com/asfixia/CustomWMS@0.1\",\n   storage: \"github\",\n   updateWMS: true,\n   cors: true\n }\n})\n&&\n[\n  { name: \"CCAR:BC250_Trecho_Rodoviario_L\", source: \"ibge\", visibility: true },\n  { name: \"my_layer\", source: \"gitHub\" }\n]"
        ],
        "returnDescription": "Always true, so the call can be chained with `&& QUERY_DESCRIPTION`."
      },
      {
        "key": "clearQueryGlobalProperties",
        "description": "Internal — undoes every side effect of the current query: deletes the globals it created,\ndestroys its remote WMS sources, resets the stored settings, the query configuration\n(`CONFIGURATION`), the dynamic CSS rules, the header/footer decoration and the projection\noptions. Called by the platform before a new query is loaded; tenant code normally does not\ncall it.",
        "type": "function()",
        "qualifiedName": "ExtjsUtils.QUERY.clearQueryGlobalProperties",
        "examples": [
          "ExtjsUtils.QUERY.clearQueryGlobalProperties();"
        ]
      },
      {
        "key": "decorate",
        "description": "Decorates the page for the current query: header logo and top-bar style, a footer, and\narbitrary CSS rules. Exactly three keys are reserved:\n - `header` {Object}: `logo: {src, style}` puts an image in the top bar's logo container\n   (`style` is an object of CSS properties applied to the container); `topbar: {style}` styles\n   the top bar itself.\n - `footer` {Object}: `html` is the footer content, `style` an object of CSS properties for it;\n   the footer container is shown and pinned to the bottom of the page.\n - `run` {Function}: called once, after the header/footer are applied — the place for\n   arbitrary code that must run when the query is decorated.\nEvery other key is passed to `CSS.defineClass(key, value)` as a CSS rule: the key is the\nselector and the value the rule body (a string such as `\"display: none;\"` or an object of\nCSS properties). So an accidental extra key becomes a CSS rule — do not put code under a\nmade-up key, use `run`. Everything is\nremoved when another query loads, and the whole call is a no-op while the query is only\nbeing parsed. Chain it with `&&` before the layer array.\n@param pageProperties {Object} `header`, `footer`, `run`, plus `selector: \"css rules\"` pairs.",
        "type": "function(pageProperties) : Boolean",
        "qualifiedName": "ExtjsUtils.QUERY.decorate",
        "examples": [
          "ExtjsUtils.QUERY.decorate({\n  header: {\n    logo: { src: \"https://example.org/logo.png\", style: { \"padding-left\": \"10px\" } },\n    topbar: { style: { \"background-color\": \"#1b5e20\" } }\n  },\n  footer: { html: \"<b>Source:</b> my institution\", style: { \"text-align\": \"center\" } },\n  run: function() { console.log(\"decorated\"); },\n  \".x-tree-node-anchor\": \"font-size: 14px;\"\n}) && QUERY_DESCRIPTION"
        ],
        "returnDescription": "Always true, so the call can be chained with `&& QUERY_DESCRIPTION`."
      },
      {
        "key": "describeQueryError",
        "description": "Internal — turns an exception thrown while evaluating a query into\n`{name, message, line?, column?}`, the position read from the stack of the evaluated code\n(Chrome `<anonymous>:L:C`, Firefox `eval:L:C`) and given in the query text - on its first\nline the evaluation wrapper's prefix is taken off the column. A SyntaxError usually has no\nposition (the browser does not give one for evaluated code). Tenant code normally does not\ncall it.\n@param exception {Error} The exception thrown by `evaluateQueryDescription`.",
        "type": "function(exception) : Object",
        "qualifiedName": "ExtjsUtils.QUERY.describeQueryError",
        "examples": [
          "var described = ExtjsUtils.QUERY.describeQueryError(new ReferenceError(\"x is not defined\"));"
        ],
        "returnDescription": "The description."
      },
      {
        "key": "evaluateQueryDescription",
        "description": "Internal — evaluates the query source text as a JavaScript expression (`eval`) and returns\nits value. This is the step where `ExtjsUtils.QUERY.setQueryGlobalProperties(...) && [...]`\nchains actually run. An empty description evaluates to `[{'name':'CSR:empty'}]`. Tenant\ncode normally does not call it.\n@param queryDescription {String} Query source text.",
        "type": "function(queryDescription) : Array|*",
        "qualifiedName": "ExtjsUtils.QUERY.evaluateQueryDescription",
        "examples": [
          "var parsed = ExtjsUtils.QUERY.evaluateQueryDescription(\"[{ name: 'CSR:estados' }]\");"
        ],
        "returnDescription": "The evaluated value; a valid query yields an array."
      },
      {
        "key": "failedSources",
        "description": "Reserved list for the remote WMS sources that failed to load. The platform currently does\nnot populate it (a failed source is simply removed from `remoteSources` and logged).",
        "type": "Array.<String>",
        "qualifiedName": "ExtjsUtils.QUERY.failedSources"
      },
      {
        "key": "getJustEvalFlag",
        "description": "Internal — returns the `justEval` flag; a side-effect API checks it to skip its work while\na query is only being parsed. Tenant code normally does not call it, although a custom\nglobal function with side effects may use it the same way.",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.QUERY.getJustEvalFlag",
        "examples": [
          "if (!ExtjsUtils.QUERY.getJustEvalFlag()) applyMySideEffect();"
        ],
        "returnDescription": "True while the query is being evaluated only for parsing."
      },
      {
        "key": "getMappiaMessage",
        "description": "Internal — extracts the Mappia payload from a raw `window` \"message\" event: returns\n`event.data.mappia_iframe`, JSON-parsed when possible (a plain string is returned as is), or\nnull when the event does not carry a `mappia_iframe` field (i.e. it was not sent through\nMappiaIO / `QUERY.postMessage`). Used by `queryState.initMessageHandler`; tenant code\nnormally does not call it.\n@param wrappedJsMsg {MessageEvent} The DOM message event received on `window`.",
        "type": "function(wrappedJsMsg) : Object|String|null",
        "qualifiedName": "ExtjsUtils.QUERY.getMappiaMessage",
        "examples": [
          "window.addEventListener(\"message\", function(evt) {\n  var msg = ExtjsUtils.QUERY.getMappiaMessage(evt);\n  if (msg) console.log(\"Mappia message\", msg);\n});"
        ],
        "returnDescription": "The parsed payload, or null when the event is not a Mappia message."
      },
      {
        "key": "getStoredQuerySettings",
        "description": "Internal — returns the arguments the current query passed to the side-effect APIs, keyed by\nfunction name (see `globalQueryStoredSettings`). The editor and the offline-maps tool use it\nto re-read the query's remote sources; tenant code normally does not call it.",
        "type": "function() : Object",
        "qualifiedName": "ExtjsUtils.QUERY.getStoredQuerySettings",
        "examples": [
          "var remoteStores = ExtjsUtils.QUERY.getStoredQuerySettings().addRemoteWMSServer || {};"
        ],
        "returnDescription": "Stored settings, e.g. `{ addRemoteWMSServer: {...}, decorate: {...} }`."
      },
      {
        "key": "globalQueryStoredSettings",
        "description": "Arguments the current query passed to the side-effect APIs, keyed by function name\n(`\"setQueryGlobalProperties\"`, `\"addRemoteWMSServer\"`, `\"decorate\"`), merged per key. The\neditor and the offline-maps tool read it back through `getStoredQuerySettings`; it is reset\nwhen another query loads.",
        "type": "Object",
        "qualifiedName": "ExtjsUtils.QUERY.globalQueryStoredSettings",
        "examples": [
          "ExtjsUtils.QUERY.globalQueryStoredSettings.decorate // the object passed to QUERY.decorate"
        ]
      },
      {
        "key": "interpretDescription",
        "description": "Internal — flattens the normalized query tree (old group syntax) into the ordered array of\nlayer definitions the map is built from. Each layer receives the properties represented by\nthe tree: `path`/`viewPath` (arrays of `PathDescription`, one per nesting level), `color`\nand `viewColor` (arrays of group colours, default `#000000`), `visibility` (default false),\n`source` (default `\"local\"`) and `definitionOrder`; the `global` key of every group and layer\nis applied through `setQueryGlobalProperties`. The result is sorted by `priority`\n(descending) and reversed so that the first definition ends up on top of the map. Tenant\ncode normally does not call it.\n@param queryDescriptionObj {Array} Normalized query array, as returned by `parseQueryDefinition`.",
        "type": "function(queryDescriptionObj) : Array.<Object>",
        "qualifiedName": "ExtjsUtils.QUERY.interpretDescription",
        "examples": [
          "var layers = ExtjsUtils.QUERY.interpretDescription(ExtjsUtils.QUERY.parseQueryDefinition(queryText, true));"
        ],
        "returnDescription": "Flat, ordered array of layer definitions (empty when the input is falsy)."
      },
      {
        "key": "justEval",
        "description": "When true the query is being evaluated only to be parsed/validated: `addRemoteWMSServer`,\n`decorate`, `CONFIGURATION.setOptions` and the `runNow` hook become no-ops. Set by\n`parseQueryObject` and read through `getJustEvalFlag`.",
        "type": "Boolean",
        "qualifiedName": "ExtjsUtils.QUERY.justEval"
      },
      {
        "key": "lastQueryError",
        "description": "The error of the last query applied through MappiaIO (`RUN_QUERY_APPLY`), or null when it\nevaluated to a list of layers: `{name, message, line?, column?}` - line and column in the\nquery text as the page sent it. Reset at the start of every apply; it is what the page\nreceives as `mappia__queryError` just before `mappia__queryApplied`.",
        "type": "Object|null",
        "qualifiedName": "ExtjsUtils.QUERY.lastQueryError",
        "examples": [
          "// inside the map page: why did the last query apply nothing?\nconsole.log(ExtjsUtils.QUERY.lastQueryError); // {name: \"ReferenceError\", message: \"TITLE is not defined\", line: 3, column: 12}"
        ],
        "default": "null"
      },
      {
        "key": "layerLoading",
        "description": "Counter of layer resources (files, GeoJSON, CSV, composed children) still loading for the\ncurrent query. It is recreated at the start of every query load. `layerLoading.addZeroCallback(fn)`\nfires `fn` when all layer loads finish — this is the standard \"wait until the layers are loaded\"\nhook for tenant code (the platform uses the same hook to zoom to the layers' extent and fire\n`QUERY_LOADED`). The callback stays registered unless it returns `false`, and only fires on a\ntransition to zero, so check `getCount()` first when nothing may be loading.",
        "type": "SharedCounter",
        "qualifiedName": "ExtjsUtils.QUERY.layerLoading",
        "examples": [
          "ExtjsUtils.QUERY.setQueryGlobalProperties({\n  runNow: function() {\n    ExtjsUtils.QUERY.layerLoading.addZeroCallback(function() {\n      console.log(\"All layers of the query are loaded\");\n    });\n  }\n}) && QUERY_DESCRIPTION"
        ]
      },
      {
        "key": "loadCurrentQuery",
        "description": "Internal — loads a whole query into the application, replacing the current one: clears the\nprevious query's globals, remote sources, CSS and decoration, removes every non-default layer,\nparses `queryDescription` (when it is a string), waits for the remote sources\n(`sourceLoading`) and then creates the layers; once `layerLoading` reaches zero it zooms to the\nlayers' extent (unless the URL has `extent`), calls `afterLoadCallback` and fires\n`QUERY_LOADED`. Wrapped in `queryState.startWaiting(\"loadCurrentQuery\")`. This is what the\nviewer, the editor and a `RUN_QUERY_APPLY` message use; tenant code normally does not call it.\n@param map {GeoExplorer} The application instance (`ExtjsUtils.JS.getApp()`), despite the parameter name.\n@param queryDescription {String|Array.<Object>} Query source text, or an already parsed query array.\n@param afterLoadCallback {function} Called after the layers were sent to load (also on failure).",
        "type": "function(map, queryDescription, afterLoadCallback) : Array.<Object>|null",
        "qualifiedName": "ExtjsUtils.QUERY.loadCurrentQuery",
        "examples": [
          "ExtjsUtils.QUERY.loadCurrentQuery(ExtjsUtils.JS.getApp(), \"[{ name: 'CSR:estados' }]\", function() { console.log(\"loaded\"); });"
        ],
        "returnDescription": "The parsed query definition on success, null when it failed to parse."
      },
      {
        "key": "normalizeLayersWithNoGroup",
        "description": "Internal — moves every top-level layer that is not inside a group into a default group\n(title `Lang.maps`, i.e. \"Maps\", color `#000000`) appended at the end, so that the\ninterpreter only ever sees groups at the top level. Works on the old (array-per-group)\nsyntax and mutates the array in place. Tenant code normally does not call it.\n@param queryDescription {Array} Parsed query in the old group syntax.",
        "type": "function(queryDescription) : Array",
        "qualifiedName": "ExtjsUtils.QUERY.normalizeLayersWithNoGroup",
        "examples": [
          "ExtjsUtils.QUERY.normalizeLayersWithNoGroup([{ name: \"CSR:estados\" }]);\n// => [[{ title: \"Maps\", color: \"#000000\" }, { name: \"CSR:estados\" }]]"
        ],
        "returnDescription": "The same array, now containing only groups."
      },
      {
        "key": "parseQueryDefinition",
        "description": "Internal — parses and validates query source text: returns the normalized query array\n(`parseQueryObject`) or null when the text does not evaluate to a non-empty array or throws\n(the error is logged to the console). Unless `keepGlobalProperties` is true the previous\nquery's globals, remote sources, CSS and decoration are cleared before parsing; they are\nalways cleared when the query turns out to be invalid. Used by the viewer/editor to load a\nquery and by `QUERY.addLayer` (with `keepGlobalProperties`); tenant code normally does not\ncall it.\n@param queryDescription {String} Query source text.\n@param keepGlobalProperties {Boolean} True to keep the current query's globals and side effects instead of clearing them first.",
        "type": "function(queryDescription, keepGlobalProperties) : Array.<Object>|null",
        "qualifiedName": "ExtjsUtils.QUERY.parseQueryDefinition",
        "examples": [
          "var parsed = ExtjsUtils.QUERY.parseQueryDefinition(\"[{ name: 'CSR:estados' }]\", true);"
        ],
        "returnDescription": "The normalized query array, or null when the query is invalid."
      },
      {
        "key": "parseQueryObject",
        "description": "Internal — turns the query source text into the normalized array the interpreter expects:\n`evaluateQueryDescription` → `parseToOldGroupSyntax` → `normalizeLayersWithNoGroup` →\n`setGroupDefaults`. With `justEval` true the `justEval` flag is raised during the evaluation,\nwhich makes side-effect APIs (`addRemoteWMSServer`, `decorate`, `CONFIGURATION.setOptions`,\n`runNow`) no-ops so the query can be validated without touching the page; the previous flag\nvalue is restored afterwards. Tenant code normally does not call it.\n@param queryDescription {String} Query source text.\n@param justEval {Boolean} True to only parse the query, suppressing its side-effect calls.",
        "type": "function(queryDescription, justEval) : Array",
        "qualifiedName": "ExtjsUtils.QUERY.parseQueryObject",
        "examples": [
          "var parsed = ExtjsUtils.QUERY.parseQueryObject(\"[{ name: 'CSR:estados' }]\", true);"
        ],
        "returnDescription": "Normalized query array in the old group syntax."
      },
      {
        "key": "parseToNewGroupSyntax",
        "description": "Internal — converts a parsed query from the old group syntax (a group is an array whose\nfirst element holds the group properties and the remaining elements are its layers) to the\nnew syntax (a group is an object with an `elements` array). Only the first level is\nconverted. Tenant code normally does not call it.\n@param queryDescription {Array} Parsed query in the old (array-per-group) format.",
        "type": "function(queryDescription) : Array",
        "qualifiedName": "ExtjsUtils.QUERY.parseToNewGroupSyntax",
        "examples": [
          "ExtjsUtils.QUERY.parseToNewGroupSyntax([[{ title: \"Group\" }, { name: \"CSR:estados\" }]]);\n// => [{ title: \"Group\", elements: [{ name: \"CSR:estados\" }] }]"
        ],
        "returnDescription": "Query with each group as `{title, color, ..., elements: [...]}`."
      },
      {
        "key": "parseToOldGroupSyntax",
        "description": "Internal — converts a parsed query from the new group syntax (a group is an object with an\n`elements` array) to the old syntax the interpreter works with (a group is an array whose\nfirst element holds the group properties and the remaining elements are its layers).\nNested groups are converted recursively; elements without `elements` are left untouched.\nTenant code normally does not call it.\n@param queryDescription {Array} Parsed query, possibly mixing both group syntaxes.",
        "type": "function(queryDescription) : Array",
        "qualifiedName": "ExtjsUtils.QUERY.parseToOldGroupSyntax",
        "examples": [
          "ExtjsUtils.QUERY.parseToOldGroupSyntax([{\n  title: \"Group 1\", color: \"#FF0000\",\n  elements: [{ name: \"CSR:layer_name_1\" }, { name: \"CSR:layer_name_2\" }]\n}]);\n// => [[{ title: \"Group 1\", color: \"#FF0000\", elements: null }, { name: \"CSR:layer_name_1\" }, { name: \"CSR:layer_name_2\" }]]"
        ],
        "returnDescription": "Query with every group as `[groupProps, layer, layer, ...]`."
      },
      {
        "key": "queryState",
        "description": "Internal — coordinates the \"apply a query\" cycle with the `postMessage` protocol used by the\neditor/calculator pages and by embedding sites (MappiaIO). It counts the operations still in\nprogress (`startWaiting`/`endWaiting`) and queues incoming messages while any is active, so\nan `applyQuery` message or a MappiaIO message is never handled in the middle of a query load.\nIts members are documented under `QueryState`. Tenant code normally does not use it.",
        "type": "Object",
        "qualifiedName": "ExtjsUtils.QUERY.queryState"
      },
      {
        "key": "remoteSources",
        "description": "Ids of the remote WMS sources registered by the current query with `addRemoteWMSServer`\n(loaded or still loading). A source that fails to load is removed from it. Cleared when\nanother query loads.",
        "type": "Array.<String>",
        "qualifiedName": "ExtjsUtils.QUERY.remoteSources",
        "examples": [
          "ExtjsUtils.QUERY.remoteSources.indexOf(\"ibge\") >= 0"
        ]
      },
      {
        "key": "removeLayer",
        "description": "Removes from the map the layer with the given name (matched against the OpenLayers layer\n`name` or its WMS `LAYERS` param). Use it together with `QUERY.addLayer` to swap layers at\nruntime.\n@param layerName {String} Full layer name, e.g. `\"CSR:estados\"`.",
        "type": "function(layerName) : Boolean",
        "qualifiedName": "ExtjsUtils.QUERY.removeLayer",
        "examples": [
          "ExtjsUtils.QUERY.removeLayer(\"CSR:estados\");"
        ],
        "returnDescription": "True when the layer was removed, false otherwise."
      },
      {
        "key": "removeLayerByReference",
        "description": "Removes a layer from the map given its layer record (as returned by `QUERY.addLayer`) or its\nOpenLayers layer object. Failures (e.g. a Google layer whose API did not initialise) are\ncaught and logged, returning false.\n@param curLayer {GeoExt.data.LayerRecord|OpenLayers.Layer} Layer record or OpenLayers layer to remove.",
        "type": "function(curLayer) : Boolean",
        "qualifiedName": "ExtjsUtils.QUERY.removeLayerByReference",
        "examples": [
          "var records = ExtjsUtils.QUERY.addLayer({ name: \"CSR:estados\" });\n// later\nExtjsUtils.QUERY.removeLayerByReference(records[0]);"
        ],
        "returnDescription": "True if the layer was removed, false otherwise."
      },
      {
        "key": "removeRemoteWMSServers",
        "description": "Internal — destroys one remote WMS source registered by the query (by id) or, without an\nargument, all of them, removing them from `remoteSources`, `app.layerSources` and\n`app.sources`. Called when a source fails to load and when another query loads; tenant code\nnormally does not call it.\n@param serverId {String} Id of the source to remove (the key used in `addRemoteWMSServer`); omit to remove all.",
        "type": "function(serverId)",
        "qualifiedName": "ExtjsUtils.QUERY.removeRemoteWMSServers",
        "examples": [
          "ExtjsUtils.QUERY.removeRemoteWMSServers(\"ibge\");"
        ]
      },
      {
        "key": "resetQueryDecoration",
        "description": "Internal — removes the page decoration applied by `QUERY.decorate`: clears the logo\ncontainer of the top bar and empties and hides the footer container. Part of\n`clearQueryGlobalProperties`; tenant code normally does not call it.",
        "type": "function()",
        "qualifiedName": "ExtjsUtils.QUERY.resetQueryDecoration",
        "examples": [
          "ExtjsUtils.QUERY.resetQueryDecoration();"
        ]
      },
      {
        "key": "resetStoredGlobalQuerySettings",
        "description": "Internal — empties `globalQueryStoredSettings`. Part of `clearQueryGlobalProperties`;\ntenant code normally does not call it.",
        "type": "function()",
        "qualifiedName": "ExtjsUtils.QUERY.resetStoredGlobalQuerySettings",
        "examples": [
          "ExtjsUtils.QUERY.resetStoredGlobalQuerySettings();"
        ]
      },
      {
        "key": "runNow",
        "description": "The query's startup hook. The query does not call this: it defines a global named `runNow`\nthrough `setQueryGlobalProperties({ runNow: function() {...} })` and the platform calls it\nonce, right after the globals of that call are registered (i.e. before the layer array is\nevaluated) and never again for the same query (`runNow.already` guards it). It is skipped\nwhile the query is only being parsed (`justEval`). Use it for one-time setup such as\n`ZOOM.limitZoomLevel`, registering `layerLoading.addZeroCallback`, custom controls or\nredirects. Because it runs before the layers exist, use `layerLoading`/`QUERY_LOADED` for\nwork that needs them.",
        "type": "function()",
        "examples": [
          "ExtjsUtils.QUERY.setQueryGlobalProperties({\n  runNow: function() {\n    ExtjsUtils.ZOOM.limitZoomLevel(17);\n    ExtjsUtils.QUERY.layerLoading.addZeroCallback(function() { console.log(\"layers loaded\"); });\n  }\n}) && QUERY_DESCRIPTION"
        ]
      },
      {
        "key": "runOnceLayerVisible",
        "description": "Runs `callback` once the given layer becomes visible. If the layer is already visible the\ncallback runs immediately; otherwise it waits for the layer's first `visibilitychanged`\nevent and then unregisters itself. The callback is invoked with the layer as `this`.\nTypically used inside a layer's `onLoad`/`runNow` code to defer work (charts, legends)\nuntil the user actually turns the layer on.\n@param layer {OpenLayers.Layer} Layer whose visibility is awaited.\n@param callback {function} Function called once with the layer as `this`.",
        "type": "function(layer, callback)",
        "qualifiedName": "ExtjsUtils.QUERY.runOnceLayerVisible",
        "examples": [
          "ExtjsUtils.QUERY.runOnceLayerVisible(ExtjsUtils.LAYER.getLayerByName(\"CSR:estados\"), function() {\n  console.log(\"Layer is now visible:\", this.name);\n});"
        ]
      },
      {
        "key": "setBaseLayerVisibility",
        "description": "Set baselayer visibility.\n\nLegacy helper: hides all backgrounds then sets the last one's visibility.\nPrefer per-layer visibility when loading a query (OpenLayers.BackgroundSelector\napplies each background's own visibility flag via applyVisibilitiesFromQuery).\n@param visibility {Boolean} The visibility applied to the last background layer.",
        "type": "function(visibility)",
        "qualifiedName": "ExtjsUtils.QUERY.setBaseLayerVisibility",
        "examples": [
          "ExtjsUtils.QUERY.setBaseLayerVisibility(false);"
        ]
      },
      {
        "key": "setGroupDefaults",
        "description": "Internal — applies each group's `defaultProperties` to every layer and subgroup inside it\n(recursively, with `Ext.applyIf`, so a value set on the layer itself or on a nearer group\nwins). See `GroupProperties.defaultProperties` for the tenant-facing behaviour. Works on the\nold (array-per-group) syntax and mutates it in place. Tenant code normally does not call it.\n@param queryDescriptionObj {Array} Parsed query in the old group syntax.",
        "type": "function(queryDescriptionObj) : Array",
        "qualifiedName": "ExtjsUtils.QUERY.setGroupDefaults",
        "examples": [
          "ExtjsUtils.QUERY.setGroupDefaults([[{ title: \"G\", defaultProperties: { opacity: 0.5 } }, { name: \"CSR:estados\" }]]);\n// the layer now has opacity: 0.5"
        ],
        "returnDescription": "The same array with the defaults applied."
      },
      {
        "key": "setJustEvalFlag",
        "description": "Internal — sets the `justEval` flag, which makes the query's side-effect APIs\n(`addRemoteWMSServer`, `decorate`, `CONFIGURATION.setOptions`, `runNow`) no-ops while a\nquery is only being parsed. Tenant code normally does not call it.\n@param status {Boolean} New value of the flag.",
        "type": "function(status)",
        "qualifiedName": "ExtjsUtils.QUERY.setJustEvalFlag",
        "examples": [
          "ExtjsUtils.QUERY.setJustEvalFlag(true);"
        ]
      },
      {
        "key": "setMappiaIoCallback",
        "description": "Registers the function that receives the messages the parent window (embedding site or the\nMappiaIO library) sends to this Mappia page/iframe with `postMessage({mappia_iframe: ...})`.\nThe callback gets the payload already unwrapped and JSON-parsed (an object or a string);\nprotocol messages are filtered out, and messages received while a query is loading are\nqueued and delivered afterwards. Registering posts `CONFIRM_QUERY_LISTENING` back to the\nparent, so the host knows it can start sending. A string with a function body is accepted\nas the callback. Use `MappiaIO.postMessage` to answer the parent.\n@param onMsgCallback {function} Callback `function(jsStr)` receiving the payload (`Object|String`) sent by the parent window.",
        "type": "function(onMsgCallback) : Boolean",
        "qualifiedName": "ExtjsUtils.QUERY.setMappiaIoCallback",
        "examples": [
          "ExtjsUtils.QUERY.setMappiaIoCallback(function(msg) {\n  if (msg && msg.type === \"geojson\") {\n    ExtjsUtils.QUERY.addLayer({ name: \"Received\", source: \"file\", type: \"json\", json: msg.geojson, visibility: true });\n    ExtjsUtils.QUERY.postMessage({ type: \"loaded\" });\n  }\n}) && QUERY_DESCRIPTION"
        ],
        "returnDescription": "Always true, so the call can be chained with `&& QUERY_DESCRIPTION`."
      },
      {
        "key": "setMessageCallback",
        "description": "Internal — alias of `QUERY.setMappiaIoCallback`: registers the function that receives the\nmessages posted by the parent window (MappiaIO). Kept for older queries; new code should\ncall `setMappiaIoCallback` directly.\n@param callbackFunction {function} Callback that receives the message sent by the parent window.",
        "type": "function(callbackFunction) : Boolean",
        "qualifiedName": "ExtjsUtils.QUERY.setMessageCallback",
        "examples": [
          "ExtjsUtils.QUERY.setMessageCallback(function(msg) { console.log(msg); }) && QUERY_DESCRIPTION"
        ],
        "returnDescription": "Always true (same as `setMappiaIoCallback`)."
      },
      {
        "key": "setQueryGlobalProperties",
        "description": "Defines globals for the query: every key of `globalProperties` becomes a `window` property\n(a value, an object or a function) that layer definitions, markup widgets (`handler=`,\n`onMark=`...) and other query code can reference by name. The names are recorded and the\nglobals are deleted when another query loads. A key that already exists on `window` and was\nnot created by the query is refused with \"Global variable can't be redefined\" in the console\n(the platform's own globals are protected; redefining one of the query's own keys is fine).\n`runNow` is the only key the platform itself invokes: right after the globals are registered\n`QUERY.runNow` is called once (see that entry). Chain it with `&&` before the layer array so\nthe globals exist when the layers are evaluated; `QUERY_DESCRIPTION` in the examples stands\nfor that array.\n@param globalProperties {Object} Object whose keys become globals; each value may be a value, an object or a function.",
        "type": "function(globalProperties) : Boolean",
        "qualifiedName": "ExtjsUtils.QUERY.setQueryGlobalProperties",
        "examples": [
          "ExtjsUtils.QUERY.setQueryGlobalProperties({\n  globalCount: 0,\n  onLayerButton: function(btn) { console.log(\"clicked\", btn); },\n  runNow: function() { ExtjsUtils.ZOOM.limitZoomLevel(17); }\n}) && [\n  { name: \"CSR:estados\", visibility: true, descriptionHtml: \"{{button|id=b1|text=Go|handler=onLayerButton}}\" }\n]"
        ],
        "returnDescription": "Always true, so the call can be chained with `&& QUERY_DESCRIPTION`."
      },
      {
        "key": "sourceLoading",
        "description": "Counter of remote WMS sources (registered with `addRemoteWMSServer`) still loading their\ncapabilities. It is recreated at the start of every query load, and the query's layers are\nonly created once it drops to zero. `sourceLoading.addZeroCallback(fn)` fires `fn` when the\nlast source finishes; the callback stays registered unless it returns `false`. The callback\nonly fires on a transition to zero, so check `getCount()` first when nothing may be loading.",
        "type": "SharedCounter",
        "qualifiedName": "ExtjsUtils.QUERY.sourceLoading",
        "examples": [
          "if (ExtjsUtils.QUERY.sourceLoading.getCount() === 0) onSourcesReady();\nelse ExtjsUtils.QUERY.sourceLoading.addZeroCallback(onSourcesReady);"
        ]
      },
      {
        "key": "storeGlobalQuerySettings",
        "description": "Internal — records the arguments a query passed to a side-effect API\n(`setQueryGlobalProperties`, `addRemoteWMSServer`, `decorate`) in\n`globalQueryStoredSettings[propertyName]`, merging the keys of `value` into what was already\nstored under that name. Tenant code normally does not call it.\n@param propertyName {String} Name of the API whose arguments are stored.\n@param value {Object} Object whose keys are merged into the stored settings.",
        "type": "function(propertyName, value)",
        "qualifiedName": "ExtjsUtils.QUERY.storeGlobalQuerySettings",
        "examples": [
          "ExtjsUtils.QUERY.storeGlobalQuerySettings(\"decorate\", { footer: { html: \"...\" } });"
        ]
      }
    ],
    "CONFIGURATION": [
      {
        "key": "_information",
        "description": "ExtjsUtils.CONFIGURATION.setOptions({...}) && [ ... ] sets options of the whole query - keepOnLeave, defaultFromProj, backgroundSelector - until another query is applied. A layer's own settings are its properties.",
        "name": "CONFIGURATION · query-wide settings (setOptions)"
      },
      {
        "key": "DEFAULT_FROM_PROJ",
        "description": "Projection assumed for vector data (GeoJSON, CSV, JSON, shapefile) that carries no\n`crs` and whose layer sets no `fromProj`, when neither the query\n(`CONFIGURATION.setOptions({ defaultFromProj })`) nor the URL\n(`?defaultFromProj=`, `options=defaultfromproj:CODE`, `options=nodefaultfromproj`)\nsays otherwise. It is the map projection (Web Mercator), so raw coordinates in\nmetres load unchanged. Exposed as `ExtjsUtils.CONFIGURATION.DEFAULT_FROM_PROJ`.\nA file layer's GeoJSON is first guessed from its first coordinate (EPSG:4326 for lon/lat values,\notherwise EPSG:3857), so for GeoJSON this default only applies when that guess fails.",
        "type": "String"
      },
      {
        "key": "setOptions",
        "description": "Defines query-level application configuration (iframe wheel behaviour, etc.).\nSettings are stored with the query and cleared when another query loads.\n@param configurationOptions {Object} Query-level configuration options.\n@param configurationOptions.keepOnLeave {Boolean} When false, iframe mouseout blocks wheel until map click.\n@param configurationOptions.defaultFromProj {String|null|false} Default fromProj for CSV and JSON lists without crs (a file layer's GeoJSON first guesses EPSG:4326 or EPSG:3857 from its first coordinate; this default is used only when that guess fails). Precedence: this option, then the URL (`?defaultFromProj=`, `options=defaultfromproj:CODE`, `options=nodefaultfromproj`), then `CONFIGURATION.DEFAULT_FROM_PROJ` (EPSG:900913). null/false/\"none\" disables.\n@param configurationOptions.backgroundSelector {Boolean|Object} When true (or options object), show the floating background basemap picker after the query loads (platform OpenLayers.BackgroundSelector.sync).",
        "type": "function(configurationOptions) : Boolean",
        "qualifiedName": "ExtjsUtils.CONFIGURATION.setOptions",
        "examples": [
          "ExtjsUtils.CONFIGURATION.setOptions({ keepOnLeave: false }) && QUERY_DESCRIPTION\nExtjsUtils.CONFIGURATION.setOptions({ defaultFromProj: 'EPSG:4674' }) && QUERY_DESCRIPTION\nExtjsUtils.CONFIGURATION.setOptions({ backgroundSelector: true }) && QUERY_DESCRIPTION"
        ],
        "returnDescription": "True for chaining with && QUERY_DESCRIPTION"
      }
    ],
    "PROJECTION": [
      {
        "key": "_information",
        "description": "ExtjsUtils.PROJECTION registers coordinate systems and normalises CRS codes, for the files a reader uploads. To say which CRS your own data is in, use fromProj on the file layer, or CONFIGURATION.setOptions({ defaultFromProj }).",
        "name": "PROJECTION · CRS codes and upload choices"
      },
      {
        "key": "normalizeCode",
        "description": "Normalizes a CRS name or code (a GeoJSON `crs.properties.name`, `\"EPSG:4674\"`, `\"epsg4326\"`,\n`\"urn:ogc:def:crs:OGC:1.3:CRS84\"`, `\"SIRGAS 2000 / UTM zone 23S\"`...) to the canonical EPSG\nstring OpenLayers uses. Matching is case/whitespace-insensitive and covers the built-in\ndefinitions (WGS 84, SIRGAS 2000 and its UTM zones, Web Mercator — `900913` maps to\n`EPSG:3857`) plus the `codes`/`patterns` registered with `setProjectionOptions`. Returns null\nfor an unknown CRS, so callers can fall back to a default projection.\n@param fullCodeName {String} CRS name or code as found in data files or user input.",
        "type": "function(fullCodeName) : String|null",
        "qualifiedName": "ExtjsUtils.PROJECTION.normalizeCode",
        "examples": [
          "ExtjsUtils.PROJECTION.normalizeCode(\"urn:ogc:def:crs:EPSG::4674\"); // \"EPSG:4674\"\nExtjsUtils.PROJECTION.normalizeCode(\"EPSG:900913\");                // \"EPSG:3857\""
        ],
        "returnDescription": "Canonical code such as `\"EPSG:4674\"`, or null when it is not recognised."
      },
      {
        "key": "resolveProjectionObject",
        "description": "Resolves a projection given in any usual form — an EPSG string, an `OpenLayers.Projection`,\nor any object with `getCode()` — to a real `OpenLayers.Projection`. The code is normalised\nwith `normalizeCode` first; when the input carries no usable code the `fallbackProjection`\n(typically the map projection) is returned instead of constructing\n`new OpenLayers.Projection(<non-string>)`, which would throw \"srsCode.indexOf is not a\nfunction\" once Proj4js is loaded. Use it before `geometry.transform(...)` when the source\nprojection comes from user data and may be missing.\n@param projection {String|OpenLayers.Projection|Object} Projection to resolve: EPSG code string, projection instance, or object exposing `getCode()`.\n@param fallbackProjection {OpenLayers.Projection} Projection returned when `projection` has no usable code (e.g. the map projection).",
        "type": "function(projection, fallbackProjection) : OpenLayers.Projection|null",
        "qualifiedName": "ExtjsUtils.PROJECTION.resolveProjectionObject",
        "examples": [
          "var mapProj = ExtjsUtils.JS.getMap().getProjectionObject();\nvar fromProj = ExtjsUtils.PROJECTION.resolveProjectionObject(geojson.crs && geojson.crs.properties.name, mapProj);\nfeature.geometry.transform(fromProj, mapProj);"
        ],
        "returnDescription": "The resolved projection, the fallback, or null when neither is available."
      },
      {
        "key": "setProjectionOptions",
        "description": "Register projection options for the current query.\nEach entry: { code, label?, proj4?, codes?, patterns? } — label defaults to code.\nThe same list drives bare-.shp upload choices and custom CRS registration.\nAlso lazy-loads theme proj4 definitions (SIRGAS) when entries are set.\n@param projectionOptions {Array|Object} Entries, or { projectionOptions, includeDefaults? }\n@param config {Object} When first arg is an array: { includeDefaults?: Boolean }\n  includeDefaults: true merges IDE defaults (EPSG:4326, EPSG:4674, EPSG:3857) before tenant entries.",
        "type": "function(projectionOptions, config) : Boolean",
        "qualifiedName": "ExtjsUtils.PROJECTION.setProjectionOptions",
        "examples": [
          "// Full explicit list (no IDE defaults added):\nExtjsUtils.PROJECTION.setProjectionOptions([\n  { code: \"EPSG:4326\", label: \"WGS 84\" },\n  { code: \"EPSG:4674\", label: \"SIRGAS 2000\" },\n  { code: \"EPSG:5534\", label: \"SAD69 / UTM 22S\",\n    proj4: \"+proj=utm +zone=22 +south +ellps=astral +units=m +no_defs\",\n    codes: [\"5534\"], patterns: [\"sad69\"] }\n]) && QUERY_DESCRIPTION",
          "// WKT instead of proj4:\nExtjsUtils.PROJECTION.setProjectionOptions([\n  { code: \"EPSG:5534\", label: \"SAD69 / UTM 22S\", wkt: \"PROJCS[...]\", codes: [\"5534\"] }\n]) && QUERY_DESCRIPTION",
          "// Only add a custom CRS; upload list = IDE defaults + EPSG:5534:\nExtjsUtils.PROJECTION.setProjectionOptions([\n  { code: \"EPSG:5534\", label: \"SAD69 / UTM 22S\", proj4: \"...\", codes: [\"5534\"] }\n], { includeDefaults: true }) && QUERY_DESCRIPTION"
        ],
        "returnDescription": "True for chaining with && QUERY_DESCRIPTION\n  { code, label?, proj4?, wkt?, codes?, patterns? }\nproj4 and wkt are alternative CRS definitions (bundled Proj4js uses proj4 internally)."
      }
    ],
    "SourceConfig": [
      {
        "key": "_information",
        "description": "Keys of a server object given to ExtjsUtils.QUERY.addRemoteWMSServer. Its url, cors, storage and updateWMS are described with <a href='#category_QUERY'>QUERY.addRemoteWMSServer</a>.",
        "name": "Server definition (addRemoteWMSServer)"
      },
      {
        "key": "onError",
        "description": "Defines callback function to be called when the store failes to be loaded.",
        "type": "function"
      },
      {
        "key": "onLoad",
        "description": "Defines callback function to be called when the store is successfully loaded.",
        "type": "function"
      },
      {
        "key": "ptype",
        "description": "The source plugin type of a layer source: the `ptype` key of a source definition given\nto `ExtjsUtils.QUERY.addRemoteWMSServer`, or of a `sources` entry of a saved map. It\ndefaults to `\"gxp_customwmscsource\"` (the platform's tiled WMS source, used whenever\n`url` is given without a `ptype`); the other registered types are the gxp plugins\n(`\"gxp_googlesource\"`, `\"gxp_osmsource\"`, `\"gxp_arcrestsource\"`, ...). The legacy\naliases `gx_wmssource`, `gx_olsource`, `gx_googlesource` and `gx_osmsource` are still\nregistered here so maps saved before version 2.3.2 (when those plugins were renamed)\nkeep loading; do not use them in new queries.",
        "type": "String",
        "examples": [
          "ExtjsUtils.QUERY.addRemoteWMSServer({\n    google: { ptype: \"gxp_googlesource\" },\n    ibge: { url: \"https://geoservicos.ibge.gov.br/geoserver/CCAR/wms?\" } // ptype defaults to \"gxp_customwmscsource\"\n}) && [\n    { name: \"HYBRID\", source: \"google\", group: \"background\", visibility: true }\n]"
        ],
        "default": "\"gxp_customwmscsource\""
      }
    ]
  },
  "Sharing Maps": {
    "_information": {
      "title": "Map links and embedding",
      "description": "A saved query opens from a link: https://maps.csr.ufmg.br/calculator/?queryid=123. Save a query in the editor to get its number; saved queries are public, and only their author can update them.\nThe parameters of the link - language, toolbar buttons (tools=), page options (options=) - change how that link opens the map; the saved query stays the same. MappiaIO is for a page that embeds the map and talks to it.",
      "example": "// Example link for sharing a map just with its visualization\nhttps://maps.csr.ufmg.br/calculator/?queryid=473\n\n// Example link for sharing a map with its Query visible\nhttps://maps.csr.ufmg.br/editor/?queryid=473"
    },
    "URLProperties": [
      {
        "key": "_information",
        "description": "Parameters of the link that opens a saved map: ?queryid=123&lang=eng&tools=...&options=... tools and options take the lists below.",
        "name": "Link parameters (?name=value)",
        "writtenAs": "?{key}="
      },
      {
        "key": "definitions",
        "description": "Loads the map's layer catalog (the WMS GetCapabilities the layers are built from)\nfrom a capabilities cache saved in this browser, instead of from the server.\n`definitions=live`, like no parameter at all, loads the default catalog: the live\none from the server (or, with `options=capabilities`, the query's own). Any other value is the\nname of a cache saved with `ExtjsUtils.OFFLINE.downloadDefinitions({name})`\n(`ExtjsUtils.OFFLINE.listDefinitions` lists them); the service worker answers\nthe catalog request from it, so the map also starts with the map server\nunreachable. Only the page opened with it is affected: other Mappia pages keep\nthe live catalog. It needs the page controlled by the service worker: without\none (the first visit, a hard reload) the page warns and loads the live catalog.\nA name with no saved cache also falls back to the live catalog.\nAn empty value (`definitions=`) starts with an empty catalog, without any\ncatalog request: the map does not wait for the server, and only layers that\nneed no catalog record show. No service worker is needed for it.\nFrom code, `ExtjsUtils.OFFLINE.useDefinitions(name)` switches a running map.",
        "type": "String",
        "examples": [
          "// the map's catalog from the cache saved as \"fazenda-42\"\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&definitions=fazenda-42",
          "// the live catalog, said explicitly\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&definitions=live",
          "// starts with an empty catalog, at once\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&definitions="
        ],
        "default": "'live'"
      },
      {
        "key": "extent",
        "description": "Defines in which part of the world the map will start on load.\nThe extents must be in EPSG:4326 (coordinates in lat,long). Also, the values need to be in the order: minX, minY, maxX, maxY, separated by comma with no spaces between them.",
        "type": "Array.<Number>",
        "examples": [
          "// Example of query in visualization mode\n// This extents start the map center at Japan\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&extent=122,24,153,45",
          "// Example of query in edit mode\n// This extents will display the whole world\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&extent=-180.0000,-90.0000,180.0000,90.0000"
        ],
        "default": "[-443.628,-16.847,-407.373,3.294]"
      },
      {
        "key": "lang",
        "description": "Define in which language the Mappia default messages and texts will be displayed.\nActually, Mappia supports the following languages:\n- Portuguese (Brazil): pt\n- English: eng",
        "type": "String",
        "examples": [
          "// Example of query in visualization mode\n// The default text will be displayed in portuguese\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&lang=pt",
          "// Example of query in edit mode\n// The default text will be displayed in english\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&lang=eng"
        ],
        "default": "'pt'"
      },
      {
        "key": "map",
        "description": "Allow the user to define which 'local' maps to load on the URL.\nYou need to pass the map name like in the ones passed at the 'name' property of a Layer with 'source' set as 'local'.\nThis property only applies for visualization mode.",
        "type": "string",
        "examples": [
          "// Example of loading a map without any 'queryid' in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?map=CSR:geologia",
          "// Example of loading two map without any 'queryid' in visualization mode\n// and starting with them visible\nhttps://maps.csr.ufmg.br/calculator/?map=CSR:geologia,CSR:estados&visiblelayers=2",
          "// Example of loading a map without any 'queryid'\nhttps://maps.csr.ufmg.br/?map=CSR:geologia"
        ],
        "default": "string.empty"
      },
      {
        "key": "options",
        "description": "Allows the user to set some configurations when the map loads, like displaying a scale, how many Layers will start visible, etc…",
        "type": "string",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&options=<options_list>",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&options=<options_list>"
        ],
        "default": "string.empty",
        "see": [
          "To learn more about the Options available and how to set them up, check their documentation at: [URL Options Section](#category_URLOptions)."
        ]
      },
      {
        "key": "queryid",
        "description": "Define which Query will be loaded when the map loads. Every saved Query at Mappia  receives a Query id number. By adding that number to the 'queryid', you can select the query loaded by the link.\nYou can find all Queries created at Mappia in the 'Query' button at the top left in the editor window. By hovering the button and selecting the 'Carregar Queries' option, a popup will show up with all the Queries created at Mappia. The older Queries are listed first.",
        "type": "Number",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=473",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=473"
        ],
        "default": "null"
      },
      {
        "key": "tools",
        "description": "Define which tools will be available for the user to interact with the map. Every tool listed here will be available for the user.\nIf left empty, Mappia will display all tools for the user.",
        "type": "string",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&tools=<tools_list>",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&tools=<tools_list>"
        ],
        "default": "'All tools available'",
        "see": [
          "To learn more about the Tools available and how to set them up, check their documentation at: [URL Tools Section](#category_URLTools)."
        ]
      },
      {
        "key": "visiblelayers",
        "description": "How many layers start visible. When the parameter is present it overrides the 'visibility' of every layer:\nall layers start hidden, then this many are turned on, counted in query order.\n   - A positive number N: the first N layers of the query start visible.\n\n   - A negative number -N: the last N layers start visible.\n\n   - 0: no layer starts visible.\n\n   - custom: the same as leaving the parameter out: each layer's own 'visibility' decides.\n\nLeaving it out (the default) lets each layer's 'visibility' decide; `options=onlyfirstvisible` is the same as `visiblelayers=1`.\nAny other text, `null` included, hides every layer.",
        "type": "Number|String",
        "examples": [
          "// Example of query in visualization mode\n// If the Layer has 3 Layers in the order: Layer1, Layer2 and Layer3,\n// By setting 'visibelayers=2', the FIRST two Layers will start visible\n// That is, the Layer1 and Layer2 will start visible\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&visiblelayers=2",
          "// Example of query in edit mode\n// If the Layer has 3 Layers in the order: Layer1, Layer2 and Layer3\n// By setting 'visiblelayers=-2', the LAST two Layers will start visible\n// That is, the Layer3 and Layer2 will start visible\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&visiblelayers=-2",
          "// Example of query in edit mode\n// By setting 'visiblelayers=0', no Layer will start visible when the map loads\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&visiblelayers=0",
          "// Example of query in visualization mode\n// By setting 'visiblelayers=custom', the Layers will start visible based on\n// its 'visibility' property. Only if 'visibility: true', the Layer will start visible\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&visiblelayers=custom"
        ],
        "default": "custom"
      }
    ],
    "URLTools": [
      {
        "key": "_information",
        "description": "Values of tools=, separated by commas: the optional buttons of the map toolbar. Listing them replaces the default set; zoom and navigation are always there.",
        "name": "Toolbar buttons (tools=)",
        "writtenAs": "tools={key}",
        "example": "// Example with all possible tools\nhttps://maps.csr.ufmg.br/calculator/?queryid=474&tools=legend,measure,hovershowlegend,getfeature,customzoom,zoomextent,helpintro,metadata"
      },
      {
        "key": "customzoom",
        "description": "If listed, will display the \"Zoom de seleção\" button. This button allows the user to drag the mouse to select a region to zoom in on the map.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&tools=customzoom",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&tools=customzoom"
        ],
        "default": "true"
      },
      {
        "key": "getfeature",
        "description": "If listed, will display the \"Indentifica atributos da feição\" button. This button, when active, allows the user to click at a specific point in the map and get the more informations of the point that was clicked.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&tools=getfeature",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&tools=getfeature"
        ],
        "default": "true"
      },
      {
        "key": "helpintro",
        "description": "Define if should display the Help Tutorial button. This button opens a simple tutorial on how to use the Mappia features.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&tools=helpintro",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&tools=helpintro"
        ],
        "default": "true"
      },
      {
        "key": "hovershowlegend",
        "description": "If listed, will display the \"Exibir da legenda da feição sob o mouse\" button. This button, when active, allows the user to see the map Legend value of where the mouse is hovering.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&tools=hovershowlegend",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&tools=hovershowlegend"
        ],
        "default": "true"
      },
      {
        "key": "legend",
        "description": "If listed, will display the Legend popup button. This button shows a popup with the Legend of the active layers.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&tools=legend",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&tools=legend"
        ],
        "default": "true"
      },
      {
        "key": "measure",
        "description": "If listed, will display the Ruler button. This button allows the user to do measurements of distances in the map.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&tools=measure",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&tools=measure"
        ],
        "default": "true"
      },
      {
        "key": "metadata",
        "description": "If listed, will display the metadata button. This button opens the description of the\nvisible maps as published - source, date, scale and the other metadata recorded with each map.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&tools=metadata"
        ],
        "default": "true"
      },
      {
        "key": "none",
        "description": "If passed as the only value for the ‘tools’ property, no tool will be available for the user to interact with the map.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&tools=none",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&tools=none"
        ],
        "default": "false"
      },
      {
        "key": "zoomextent",
        "description": "If listed, will display the \"Zoom nos layer\" button. The button that centers the screen at the actual visible layer.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&tools=zoomextent",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&tools=zoomextent"
        ],
        "default": "true"
      }
    ],
    "URLOptions": [
      {
        "key": "_information",
        "description": "Values of options=, separated by commas, that change how the page opens the map. Some also exist as a layer key or a query setting; the guide Where to find it says which one wins.",
        "name": "Page options (options=)",
        "writtenAs": "options={key}",
        "example": "// Example with all possible options\nhttps://maps.csr.ufmg.br/calculator/?queryid=474&options=capabilities,grid,scale,disabledownload,hidemetadata,overview,onlyfirstvisible"
      },
      {
        "key": "capabilities",
        "description": "If listed, will load only the maps defined in the 'name' property of the Layers. This can speed up the loading time of the Query.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&options=capabilities",
          "// Example of query in editor mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&options=capabilities"
        ],
        "default": "false"
      },
      {
        "key": "disabledownload",
        "description": "If listed, will hide the “download” button of the Legend Window.\nThe “download” button is the same one as the “paramButtonConfig” “download” type.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&options=disabledownload",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&options=disabledownload"
        ],
        "default": "false"
      },
      {
        "key": "grid",
        "description": "If listed, will display the parallel and meridian lines grid.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&options=grid",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&options=grid"
        ],
        "default": "false"
      },
      {
        "key": "hidemetadata",
        "description": "If listed, will hide the “metadata” button of the Legend Window.\nThe “metadata” button is the same one as the “paramButtonConfig” “metadata” type.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&options=hidemetadata",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&options=hidemetadata"
        ],
        "default": "false"
      },
      {
        "key": "hidestylechooser",
        "description": "If listed, hides the style chooser of every layer in the layer panel, so the reader sees each\nmap only in the style the query selected. The per-layer equivalent is the layer property\n`hideStyleChooser`.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&options=hidestylechooser"
        ],
        "default": "false"
      },
      {
        "key": "keeponleave",
        "description": "Controls mouse wheel zoom when the map is embedded in an iframe.\n\nOnly applies when the page runs inside an iframe. When false, leaving the\ndocument (mouseout with destination HTML) blocks wheel zoom until the user\nclicks the map; wheel events over the viewport then show Lang.navigationWheelDisabled.",
        "type": "Boolean",
        "examples": [
          "// Query-level setting (preferred for shared maps)\nExtjsUtils.CONFIGURATION.setOptions({ keepOnLeave: false }) && QUERY_DESCRIPTION",
          "// Plugin config (tools array in calculator / composer)\n{ ptype: \"gxp_usabilityhelper\", keepOnLeave: false }",
          "// URL option (sets keepOnLeave via advToolsOptions; same as default true)\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&options=keeponleave"
        ],
        "default": "true"
      },
      {
        "key": "onlyfirstvisible",
        "description": "If listed, only the first Layer defined at the Query will be visible when the map loads.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&options=onlyfirstvisible",
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&options=onlyfirstvisible"
        ],
        "default": "false"
      },
      {
        "key": "overview",
        "description": "If listed, will display a zoom out interactable window at the bottom right when the user zooms in the map.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&options=overview",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&options=overview"
        ],
        "default": "false"
      },
      {
        "key": "scale",
        "description": "If listed, will display the scale of the map at the bottom left.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&options=scale",
          "// Example of query in edit mode\nhttps://maps.csr.ufmg.br/editor/?queryid=<query_id>&options=scale"
        ],
        "default": "false"
      },
      {
        "key": "startopened",
        "description": "If listed, the layer panel (the Legend Window with the query's groups and layers) starts open\nwhen the map loads, instead of collapsed behind its button. Use it when the layer list is part\nof what the page is for; leave it out to give the map the whole screen.",
        "type": "Boolean",
        "examples": [
          "// Example of query in visualization mode\nhttps://maps.csr.ufmg.br/calculator/?queryid=<query_id>&options=startopened"
        ],
        "default": "false"
      }
    ],
    "MappiaIO": [
      {
        "key": "_information",
        "completeAPI": "Connects your page to a Mappia map embedded in it, in both directions. Your page loads <code>https://maps.csr.ufmg.br/mappia_io.js</code>, connects with <code>MappiaIO(iframe, true)</code>, loads queries with <code>applyQuery</code>, sends objects with <code>send</code> and receives with <code>addOnMessageCallback</code>. Inside the map, the query receives with <code>ExtjsUtils.QUERY.setMappiaIoCallback</code> and answers with <code>ExtjsUtils.QUERY.postMessage</code>. Your page's buttons, lists and forms can drive the map (filter, zoom, highlight, edit a record), and the map tells the page what the user clicked, hovered or drew.",
        "description": "Connects your page to an embedded Mappia map: apply queries, send messages, receive the map's.",
        "name": "MappiaIO · your page and the map"
      },
      {
        "key": "MappiaIO",
        "description": "Connects your page to a Mappia map in an iframe (or in a window you opened) and returns the\nconnection: `applyQuery` loads a query into the map, `send` delivers an object to the query\nrunning there, `addOnMessageCallback` receives what the query sends back with\n`ExtjsUtils.QUERY.postMessage`. Load the library from the map server\n(`<script src=\"https://maps.csr.ufmg.br/mappia_io.js\">`).\n\nOn creation it greets the map (`mappia__checkDOM`) until the map answers it is listening\n(`mappia__confirmDOM`); everything sent before that is queued and delivered in order, so\n`applyQuery` and `send` may be called right away. Messages travel through `window.postMessage`\nwrapped as `{mappia_iframe: <JSON text>}`, so the page and the map may be on different sites.\n\nDo not open the map with `noopener` or `noreferrer` (on the iframe or in `window.open`): they\ncut the window link the messages travel on. `window.open(url, \"_blank\", \"width=1280,height=860\")`\nworks; `window.open(url, \"_blank\", \"noopener,noreferrer\")` does not.\n@param communicationTarget {String|HTMLIFrameElement|Window} The map to talk to: the id of its\niframe, the iframe element itself, or the window returned by `window.open`.\n@param keepAfterReady {Boolean} `true` keeps listening after the handshake - needed by any\npage that receives messages from the map or applies more than one query. With `false` the\nconnection stops listening once the map has answered.",
        "type": "function(communicationTarget, keepAfterReady) : Object",
        "examples": [
          "<iframe id=\"mappia\" src=\"https://maps.csr.ufmg.br/calculator/?lang=eng&options=scale\"></iframe>\n<script src=\"https://maps.csr.ufmg.br/mappia_io.js\"></script>\n<script>\n  var mappia = MappiaIO(\"mappia\", true);\n  mappia.addOnMessageCallback(function (msg) { console.log(\"from the map:\", msg); });\n  mappia.applyQuery('[{ name: \"CSR:estados\", title: \"States\", visibility: true }]');\n  mappia.send({ operation: \"hello\", message: \"from the page\" });\n</script>"
        ],
        "returnDescription": "The connection, whose methods return it again so calls can be chained."
      },
      {
        "key": "addOnMessageCallback",
        "description": "Registers a function that receives every message from the map. What the query posts with\n`ExtjsUtils.QUERY.postMessage(object)` arrives as that object; the platform's own notices\narrive as strings starting with `mappia__` - `mappia__queryApplied` once a query sent with\n`applyQuery` is in place, `mappia__confirmQueryListening` when a query registers its\ncallback. Several functions may be registered; each gets every message, about 150 ms after\nit was posted. Needs a connection created with `keepAfterReady` true.\n@param callback {function} `function (message)`, called for each message.",
        "type": "function(callback) : Object",
        "examples": [
          "mappia.addOnMessageCallback(function (msg) {\n  if (msg === \"mappia__queryApplied\") return showStatus(\"Map ready\");\n  if (msg && msg.operation === \"city_clicked\") showDetails(msg.message);\n});"
        ],
        "returnDescription": "The connection."
      },
      {
        "key": "addOnRemoveCallback",
        "description": "Registers a function run when `remove` stops the connection, to tidy up whatever the page\nattached to it.\n@param callback {function} Called with no arguments.",
        "type": "function(callback) : Object",
        "returnDescription": "The connection."
      },
      {
        "key": "addReadyCallback",
        "description": "Runs `callback` once the map has answered the handshake - at once when it already has.\nThe place to start a conversation that needs the map listening: apply the first query\nthere, then send the page's starting state (filters, the record being edited...).\n@param callback {function} Called with no arguments.",
        "type": "function(callback) : Object",
        "examples": [
          "mappia.addReadyCallback(function () {\n  fetch(\"/my-map-query.js\").then(function (r) { return r.text(); }).then(function (text) {\n    mappia.applyQuery(text);\n    mappia.send({ operation: \"setFilters\", message: currentFilters });\n  });\n});"
        ],
        "returnDescription": "The connection."
      },
      {
        "key": "applyQuery",
        "description": "Loads a query into the map - the same text you would type in the Mappia editor, of any\nlength and not saved on the server - replacing the layers the map shows. The map confirms\nwith the message `mappia__queryApplied` once the new layers are in place. A query that\ndid not run (a syntax or runtime error, or a value that is not a list of layers) is\nconfirmed too, but the message `{action: \"mappia__queryError\", error: {name, message,\nline, column}}` comes first. One connection can apply query after query; called before\nthe handshake, it waits in the queue.\n@param queryContent {String} The query text.",
        "type": "function(queryContent) : Object",
        "examples": [
          "mappia.addOnMessageCallback(function (msg) {\n  if (msg && msg.action === \"mappia__queryError\") console.warn(\"The query did not run:\", msg.error.message, \"line\", msg.error.line);\n  else if (msg === \"mappia__queryApplied\") console.log(\"applied\");\n});\nmappia.applyQuery('[{ name: \"CSR:altimetria\", title: \"Elevation\", visibility: true }]');"
        ],
        "returnDescription": "The connection (not a promise: listen for `mappia__queryApplied`)."
      },
      {
        "key": "applyQueryFromUrl",
        "description": "Fetches the query text at `queryUrl` and applies it as `applyQuery` does - for a query\nkept as a file in your own site or repository. The URL is read with `fetch`, so one on\nanother site must allow cross-origin reads.\n@param queryUrl {String} Address of a file holding query text, such as `[{ name: \"CSR:estados\" }]`.",
        "type": "function(queryUrl) : Promise.<Object>",
        "examples": [
          "mappia.applyQueryFromUrl(\"/queries/my-map.js\");"
        ],
        "returnDescription": "Resolves with the connection once the text was fetched and sent -\rnot when the map applied it: wait for the `mappia__queryApplied` message for that."
      },
      {
        "key": "isDomLoaded",
        "description": "Whether the map has answered the handshake. Until it has, `send` and `applyQuery` queue\ntheir messages and deliver them, in order, as soon as it answers.",
        "type": "function() : Boolean",
        "returnDescription": "`true` once the map is listening."
      },
      {
        "key": "postMessage",
        "description": "Sends a message to the Mappia/iframe, allowing communication between the Mappia/iframe and the parent window. (Compatible with MappiaIO library)\nThe 'QUERY.setMappiaIoCallback' is responsible to interpret the sent message.\n@param jsStr {Object|String} Object to be sent from inside Mappia/iframe to parent window. Objects are JSON-stringified; the parent receives `{ mappia_iframe: jsStr }`.",
        "type": "function(jsStr)",
        "qualifiedName": "ExtjsUtils.QUERY.postMessage",
        "examples": [
          "ExtjsUtils.QUERY.postMessage({a:1,b:2})"
        ]
      },
      {
        "key": "remove",
        "description": "Stops listening to the map and runs the functions registered with `addOnRemoveCallback`.\nA connection created with `keepAfterReady` false calls it by itself right after the\nhandshake; call it yourself when the page drops the map (a closed panel, a route change)\nso an old connection does not keep receiving messages.",
        "type": "function() : Object",
        "returnDescription": "The connection."
      },
      {
        "key": "send",
        "description": "Delivers `message` to the query running in the map, which receives it in the function it\nregistered with `ExtjsUtils.QUERY.setMappiaIoCallback`. The shape is yours to choose; by\nconvention an object naming an `operation` and carrying a `message`, plus a `requestId`\nwhen the page waits for an answer. Objects travel as JSON (functions and dates do not\nsurvive); a message carrying a File or Blob (`{operation: \"import\", file: file}`) is sent\nas the object itself, so the file reaches the query intact. Never send strings starting\nwith `mappia__`: the platform reserves them.\n@param message {Object|String} What to deliver to the query.\n@param forceSend {Boolean} Sends at once even before the handshake, skipping the\nqueue. Pages normally leave it out.",
        "type": "function(message, forceSend) : Object",
        "examples": [
          "mappia.send({ operation: \"show_city\", message: { name: \"Belo Horizonte\" } });\nmappia.send({ operation: \"list_cities\", requestId: \"r1\" }); // the query answers with the same requestId"
        ],
        "returnDescription": "The connection."
      }
    ],
    "QueryState": [
      {
        "key": "_information",
        "description": "Synchronisation between the parent page (mappia_io.js) and the query being applied: while a query is loading, incoming apply messages are queued and flushed when the wait counter reaches zero. Internal to the platform; listed so embed authors understand the sequence.\nUsage: ExtjsUtils.QUERY.queryState",
        "name": "QUERY.queryState · apply-message queue (internal)"
      },
      {
        "key": "cancelWaiting",
        "description": "Internal — releases the operation started with `startWaiting(name)` when it failed\n(e.g. the query did not parse). It currently behaves exactly like `endWaiting(name)`:\nthe counter is decremented and, at zero, the queued messages are still processed —\nthey are not discarded. Tenant code normally does not call it.\n@param name {String} Identifier used in the matching `startWaiting` call.",
        "type": "function(name)",
        "examples": [
          "ExtjsUtils.QUERY.queryState.cancelWaiting(\"myAsyncSetup\");"
        ]
      },
      {
        "key": "endWaiting",
        "description": "Internal — marks the end of the operation started with `startWaiting(name)`. Decrements\nthe shared counter; when it reaches zero every queued message is processed in order\n(`RUN_QUERY_APPLY` messages apply their query, the others go to the MappiaIO callback).\nA name that is not active is rejected with a console error. Tenant code normally does\nnot call it.\n@param name {String} Identifier used in the matching `startWaiting` call.",
        "type": "function(name)",
        "examples": [
          "ExtjsUtils.QUERY.queryState.endWaiting(\"myAsyncSetup\");"
        ]
      },
      {
        "key": "handleMessage",
        "description": "Internal — entry point for a `RUN_QUERY_APPLY` message (`{action, queryContent}`). While\nwaiting the message is queued; otherwise it is wrapped in a `startWaiting`/`endWaiting`\npair so that it is applied through the queue right away. Tenant code normally does not\ncall it.\n@param jsMsg {Object} Message object with `action` and `queryContent`.",
        "type": "function(jsMsg)"
      },
      {
        "key": "initMessageHandler",
        "description": "Internal — installs the `window` \"message\" listener that receives the parent page's\n`postMessage` calls and immediately posts `CONFIRM_DOM_LOADED` back. Protocol messages\n(`CHECK_DOM`, `CHECK_QUERY`, `RUN_QUERY_APPLY`) are answered by the platform; any other\nmessage is queued while `isWaiting()` is true, otherwise it is delivered to the callback\nregistered with `QUERY.setMappiaIoCallback`. Called once by the editor/calculator pages\nwhen they load; tenant code does not call it.",
        "type": "function()"
      },
      {
        "key": "isWaiting",
        "description": "Internal — tells whether at least one operation registered with `startWaiting` is still\nrunning, i.e. whether incoming messages are being queued. Tenant code normally does not\ncall it.",
        "type": "function() : Boolean",
        "examples": [
          "if (!ExtjsUtils.QUERY.queryState.isWaiting()) handleNow();"
        ],
        "returnDescription": "True while the shared counter is greater than zero."
      },
      {
        "key": "queueMessage",
        "description": "Internal — appends a message to the queue that is flushed when the counter reaches zero.\nMeant to be called only while `isWaiting()` is true; a queued `RUN_QUERY_APPLY` message\napplies its query, any other message is delivered to the MappiaIO callback. Tenant code\nnormally does not call it.\n@param message {*} Message payload as received from the parent window.",
        "type": "function(message)"
      },
      {
        "key": "setQueryApplyHandler",
        "description": "Internal — replaces the default way a `RUN_QUERY_APPLY` message is applied\n(`QUERY.loadCurrentQuery`) with a custom handler; the editor uses it to load the received\nquery text into its code panel instead of straight into the map. The handler runs once the\nmap is loaded (or is deferred to `AFTER_APP_LOADING`) and receives\n`(queryContent, notifyQueryApplied)`; it must call `notifyQueryApplied()` when done so the\nparent window gets `CONFIRM_QUERY_APPLIED`. Tenant code normally does not call it.\n@param handler {function} Function `(queryContent, notifyQueryApplied)` that applies the query.",
        "type": "function(handler)",
        "examples": [
          "ExtjsUtils.QUERY.queryState.setQueryApplyHandler(function(queryContent, notifyQueryApplied) {\n  ExtjsUtils.QUERY.loadCurrentQuery(ExtjsUtils.JS.getApp(), queryContent, notifyQueryApplied);\n});"
        ]
      },
      {
        "key": "startWaiting",
        "description": "Internal — marks the start of an operation (a query load, the map loading) during which\nincoming messages must be queued instead of handled. Increments the shared counter under\n`name`; a name that is already active is rejected with a console error. Pair it with\n`endWaiting(name)` (success) or `cancelWaiting(name)` (failure). Tenant code normally does\nnot call it.\n@param name {String} Identifier of the operation, e.g. `\"loadCurrentQuery\"`.",
        "type": "function(name)",
        "examples": [
          "ExtjsUtils.QUERY.queryState.startWaiting(\"myAsyncSetup\");"
        ]
      }
    ]
  },
  "Helper Functions": {
    "_information": {
      "title": "ExtjsUtils helpers (A-Z)",
      "description": "Functions your own code calls - in a button handler, beforeCalc, runNow - as ExtjsUtils.NAMESPACE.member, sorted by namespace. Most used (tutorial step 8): <a href='#category_Alertify'>ALERTIFY</a>, <a href='#category_Layer'>LAYER</a>, <a href='#category_JS'>JS</a>, <a href='#category_ZOOM'>ZOOM</a>, <a href='#category_Request'>REQUEST</a>, <a href='#category_GEOJSON'>GEOJSON</a>, <a href='#category_CSS'>CSS</a>.\nQUERY, CONFIGURATION and PROJECTION are under Query setup; OFFLINE under Offline maps."
    },
    "ExtjsUtils": [
      {
        "key": "_information",
        "description": "Helpers defined directly on the ExtjsUtils object (not inside a namespace).\nUsage: ExtjsUtils.&lt;member&gt;",
        "name": "ExtjsUtils · top-level helpers"
      },
      {
        "key": "ASSERTION_ENABLED",
        "description": "When `true`, the `console.assert` checks spread through the platform code are evaluated\n(see `dealWithAssertions`); when `false` they are turned into console warnings. Meant to be\n`true` only in development builds.",
        "type": "Boolean",
        "qualifiedName": "ExtjsUtils.ASSERTION_ENABLED"
      },
      {
        "key": "CancelEvent",
        "description": "Cancels the default browser action of a DOM event (`preventDefault`, or `returnValue = false`\non old IE) — for instance to suppress the context menu or a link navigation.\n@param evt {Event} The DOM event to cancel.",
        "type": "function(evt)",
        "qualifiedName": "ExtjsUtils.CancelEvent",
        "examples": [
          "ExtjsUtils.addDomListener(document.getElementById('map'), 'contextmenu', ExtjsUtils.CancelEvent);"
        ]
      },
      {
        "key": "DomObserver",
        "description": "Ext plugin that binds plain DOM events (the ones the Ext component itself does not expose,\nsuch as `contextmenu`) to the component's element as soon as it renders. Create it with an\nobject mapping DOM event names to handlers `function(evt, component)` — or with `{ listeners:\n{...} }` — and put the instance in the component's `plugins` array.",
        "type": "function",
        "qualifiedName": "ExtjsUtils.DomObserver",
        "examples": [
          "{\n  xtype: 'gxp_fieldset',\n  plugins: [\n    new ExtjsUtils.DomObserver({\n      contextmenu: function(evt, comp) {\n        ExtjsUtils.CancelEvent(evt);\n      }\n    })\n  ]\n}"
        ]
      },
      {
        "key": "NodeMouseoverPlugin",
        "description": "Ext plugin for an `Ext.tree.TreePanel` that makes the tree fire `mouseover` and `mouseout`\nevents for its nodes (`(node, evt)`), which Ext 3.4 does not provide out of the box. Add an\ninstance to the tree's `plugins` and listen to those events on the tree.",
        "type": "function",
        "qualifiedName": "ExtjsUtils.NodeMouseoverPlugin",
        "examples": [
          "var tree = new Ext.tree.TreePanel({\n  plugins: [new ExtjsUtils.NodeMouseoverPlugin()],\n  listeners: {\n    mouseover: function(node, evt) { console.log('over', node.text); },\n    mouseout: function(node, evt) { console.log('out', node.text); }\n  }\n});"
        ]
      },
      {
        "key": "SliderZIndexFixer",
        "description": "Ext plugin that works around a `GeoExt.LayerOpacitySlider` bug: after each drag the slider thumb\nkeeps a raised z-index, so the plugin resets the thumb's `z-index` on `dragend` to the value\ngiven to the constructor (default `0`).",
        "type": "function",
        "qualifiedName": "ExtjsUtils.SliderZIndexFixer",
        "examples": [
          "new GeoExt.LayerOpacitySlider({\n  layer: layer,\n  plugins: new ExtjsUtils.SliderZIndexFixer(0)\n});"
        ]
      },
      {
        "key": "addDomListener",
        "description": "Adds a DOM event listener, working across old and new browsers (`addEventListener` or\n`attachEvent`). The usual query use is `addDomListener(window, 'mousemove', fn)` to track the\nelement under the mouse for a floating popup. Remove it later with `removeDomListener` passing\nthe very same function.\n@param domObj {EventTarget} DOM object to listen on (an element, `document` or `window`).\n@param evtName {String} DOM event name, without the `on` prefix (e.g. `'mousemove'`, `'click'`).\n@param fn {function} Event callback; receives the DOM event.\n@param useCapture {Boolean} Register in the capture phase. Only honoured when `domObj` is `window`, `document` or `document.body`; other targets always use the bubbling phase.",
        "type": "function(domObj, evtName, fn, useCapture)",
        "qualifiedName": "ExtjsUtils.addDomListener",
        "examples": [
          "ExtjsUtils.addDomListener(window, 'mousemove', function(evt) {\n  window.lastHoveredElement = evt.target;\n});"
        ]
      },
      {
        "key": "addListenerToConfig",
        "description": "Adds a listener to an Ext config object's `listeners[eventName]` without losing a listener\nthat is already there: when one exists, the new callback runs first and then the previous\none, both receiving the event arguments. Creates the `listeners` object when missing.\n@param config {Object} Ext component config object to modify.\n@param eventName {String} Name of the Ext event (e.g. `'afterrender'`, `'click'`).\n@param callback {function} Listener to add.\n@param scope {Object} `this` for `callback`; defaults to the event's own scope.",
        "type": "function(config, eventName, callback, scope) : Object",
        "qualifiedName": "ExtjsUtils.addListenerToConfig",
        "examples": [
          "var cfg = { xtype: 'button', text: 'Go', listeners: { click: function() { console.log('first'); } } };\nExtjsUtils.addListenerToConfig(cfg, 'click', function() { console.log('added'); });\n// clicking now logs \"added\" then \"first\""
        ],
        "returnDescription": "The same config object, modified."
      },
      {
        "key": "bboxTransform",
        "description": "Reprojects a bounding box from one projection to another and returns it as a new\n`OpenLayers.Bounds` (the input is never modified). The source projection defaults to lon/lat\n(`\"EPSG:4326\"`), which is what WMS capabilities bounding boxes use.\n@param layerBBox {OpenLayers.Bounds|Array.<Number>} The bounding box to reproject: an `OpenLayers.Bounds` or a `[left, bottom, right, top]` array.\n@param fromProjection {String|OpenLayers.Projection} Projection the bounding box is currently in.\n@param toProjection {String|OpenLayers.Projection} Projection to convert to (e.g. the map projection, `app.mapPanel.map.getProjection()`).",
        "type": "function(layerBBox, fromProjection, toProjection) : OpenLayers.Bounds",
        "qualifiedName": "ExtjsUtils.bboxTransform",
        "examples": [
          "// lon/lat box of Minas Gerais into the map projection, then zoom to it\nvar bounds = ExtjsUtils.bboxTransform([-51.1, -22.9, -39.9, -14.2], \"EPSG:4326\", app.mapPanel.map.getProjection());\napp.mapPanel.map.zoomToExtent(bounds);"
        ],
        "returnDescription": "A new bounds object in `toProjection`."
      },
      {
        "key": "dealWithAssertions",
        "description": "Wraps `console.assert` so the assertions left in the platform code follow\n`ASSERTION_ENABLED` (read once, when this runs): when enabled they go through the native\n`console.assert`; when disabled every `console.assert` call is turned into a `console.warn`\nof its arguments instead. Called once when this file is initialised; a query has no reason\nto call it.",
        "type": "function()",
        "qualifiedName": "ExtjsUtils.dealWithAssertions",
        "examples": [
          "ExtjsUtils.ASSERTION_ENABLED = false;\nExtjsUtils.dealWithAssertions(); // from now on console.assert only warns"
        ]
      },
      {
        "key": "deepCompare",
        "description": "Deep equality of two values: every property is compared recursively (arrays, nested objects,\ndates, regular expressions and functions — the latter by their source text — with cycle\nprotection). An optional hook lets you decide the comparison of any pair of values yourself.\nUseful to detect whether a layer definition or an inputs object actually changed.\n@param objA {*} First value to compare.\n@param objB {*} Second value to compare.\n@param customCompare {function} Hook `function(x, y)` called for every pair of values (the roots first, then each nested property) before the default comparison. Return `true` (equal) or `false` (different) to decide, or `undefined` to let the default comparison run.",
        "type": "function(objA, objB, customCompare) : Boolean",
        "qualifiedName": "ExtjsUtils.deepCompare",
        "examples": [
          "ExtjsUtils.deepCompare({ a: 1, b: [1, 2] }, { a: 1, b: [1, 2] }); // true\n// Treat numbers closer than 0.001 as equal\nExtjsUtils.deepCompare(oldStats, newStats, function(x, y) {\n  if (typeof x === 'number' && typeof y === 'number') return Math.abs(x - y) < 0.001;\n});"
        ],
        "returnDescription": "`true` when the two values are deeply equal, `false` otherwise."
      },
      {
        "key": "default_background_map",
        "description": "Layer definition of the platform's default basemap (OpenStreetMap Mapnik, from the `osm`\nsource), used when a query declares no layer with `group: \"background\"`: the platform then\ninserts this definition at the bottom of the query's layer list while the query is loaded.\nTo use another basemap, declare your own `group: \"background\"` layer in the query instead of\nchanging this object — production queries that mutate it at runtime (e.g. from `runNow`) were\nverified to have no effect, because it is read before the query code runs.",
        "type": "Object",
        "qualifiedName": "ExtjsUtils.default_background_map",
        "examples": [
          "// The declarative way to replace the default basemap\n[\n  {\n     title: 'Basemaps',\n     elements: [\n        { title: 'Google Hybrid', name: 'HYBRID', source: 'google', group: 'background', visibility: true }\n     ]\n  }\n]"
        ]
      },
      {
        "key": "disableContextMenu",
        "description": "Disables the browser context menu (right click) on an Ext component by adding a `DomObserver`\nplugin that cancels the `contextmenu` event. Apply it to the component's config object before\nthe component is created; it does nothing on an already built component (one that has `on`).\n@param configObj {Object} Config object of the Ext component to be created.",
        "type": "function(configObj) : Object",
        "qualifiedName": "ExtjsUtils.disableContextMenu",
        "examples": [
          "var panel = new Ext.Panel(ExtjsUtils.disableContextMenu({ html: 'No right click here' }));"
        ],
        "returnDescription": "The same config object, with the context-menu-cancelling plugin added."
      },
      {
        "key": "getBodyHeight",
        "description": "Returns the full height of the page document in pixels (the largest of the body/document\nscroll, offset and client heights), including the part that is scrolled out of view.",
        "type": "function() : Number",
        "qualifiedName": "ExtjsUtils.getBodyHeight",
        "examples": [
          "var pageHeight = ExtjsUtils.getBodyHeight();"
        ],
        "returnDescription": "The document height in pixels."
      },
      {
        "key": "getButtonScale",
        "description": "Returns the Ext button `scale` to use so buttons are usable both on desktop and on touch\ndevices: `'large'` on mobile (`CHECK.isMobile()`), `'medium'` otherwise.\n@param strict {Boolean} Currently unused; the check always uses the non-strict `CHECK.isMobile()`.",
        "type": "function(strict) : String",
        "qualifiedName": "ExtjsUtils.getButtonScale",
        "examples": [
          "new Ext.Button({ text: 'Apply', scale: ExtjsUtils.getButtonScale() });"
        ],
        "returnDescription": "`'large'` or `'medium'`, ready for an Ext button `scale` config."
      },
      {
        "key": "getDesiredProjection",
        "description": "Returns the \"desired\" output projection for coordinates shown to users: longitude/latitude,\n`\"EPSG:4326\"`. Use it as the target projection when converting a clicked map position into\ncoordinates a person will read. It is not the map projection — see `getFromProjection`.",
        "type": "function() : String",
        "qualifiedName": "ExtjsUtils.getDesiredProjection",
        "examples": [
          "// Longitude/latitude of a map click, ready to be displayed\nvar lonLat = ExtjsUtils.COORDINATE.getLatLong(evt, ExtjsUtils.getDesiredProjection());"
        ],
        "returnDescription": "The projection code `\"EPSG:4326\"`."
      },
      {
        "key": "getElementsByClassName",
        "description": "Cross-browser `getElementsByClassName`: returns the elements carrying all the given class\nnames (space separated), optionally restricted to a tag name and to the descendants of an\nelement. Uses the native method, `querySelectorAll`, XPath or a manual scan depending on the\nbrowser, and always returns a real array. Original polyfill by Robert Nyman, refactored by\nTubal Martin.\n@param className {String} One or more class names, separated by spaces (all must be present).\n@param tag {String} Tag name to restrict the result to (e.g. `'div'`); any tag when omitted.\n@param elm {HTMLElement} Element to search under; the whole document when omitted.",
        "type": "function(className, tag, elm) : Array.<HTMLElement>",
        "qualifiedName": "ExtjsUtils.getElementsByClassName",
        "examples": [
          "ExtjsUtils.getElementsByClassName('legend-entry selected', 'div').forEach(function(el) {\n  el.style.fontWeight = 'bold';\n});"
        ],
        "returnDescription": "The matching elements (empty when none)."
      },
      {
        "key": "getFromProjection",
        "description": "Returns the map projection code, `\"900913\"` (Spherical Mercator, the projection every layer is\nrendered in). This is a different concept from `CONFIGURATION.DEFAULT_FROM_PROJ` — the\nprojection assumed for vector/GeoJSON input that declares no CRS — even though both happen to\nbe the same projection.",
        "type": "function() : String",
        "qualifiedName": "ExtjsUtils.getFromProjection",
        "examples": [
          "// Reproject a lon/lat bounding box into map units\nvar bounds = ExtjsUtils.bboxTransform([-50, -20, -40, -10], \"EPSG:4326\", ExtjsUtils.getFromProjection());"
        ],
        "returnDescription": "The map projection code `\"900913\"`."
      },
      {
        "key": "getFunction",
        "description": "Always returns a callable function from a \"function definition\". A function is returned as is\n(its closure is kept); a string is resolved first as the name of a global function\n(`window[name]`) and, failing that, evaluated as function source (a `function(...) {...}`\nliteral or a statement body) through `Mark.getFunction`; anything else yields a no-op\nfunction. This is the resolution the markup widgets apply to string parameters such as\n`handler=` in `{{button|handler=myGlobalFunction}}`.\n@param definition {function|String|*} A function, the name of a global function, function source code, or anything else (no-op).",
        "type": "function(definition) : function",
        "qualifiedName": "ExtjsUtils.getFunction",
        "examples": [
          "window.sayHi = function() { ExtjsUtils.ALERTIFY.log(\"Hi\"); };\nExtjsUtils.getFunction(\"sayHi\")();                          // calls window.sayHi\nExtjsUtils.getFunction(\"function() { return 42; }\")();       // 42\nExtjsUtils.getFunction(undefined)();                          // does nothing"
        ],
        "returnDescription": "The resolved function; never `undefined`."
      },
      {
        "key": "getLayerExtent",
        "description": "Deprecated alias of `ExtjsUtils.LAYER.getLayerExtent`: returns the extent of a layer in the map\nprojection (its WMS lon/lat bounding box reprojected), falling back to the layer's or the map's\n`maxExtent`. Kept because many queries still call it; new code should call\n`ExtjsUtils.LAYER.getLayerExtent` directly.\n@param layer {OpenLayers.Layer} The layer whose extent is wanted.",
        "type": "function(layer) : OpenLayers.Bounds",
        "qualifiedName": "ExtjsUtils.getLayerExtent",
        "examples": [
          "// Zoom the map to a layer (e.g. from a layer's onVisibilityChange)\napp.mapPanel.map.zoomToExtent(ExtjsUtils.getLayerExtent(layer), true);"
        ],
        "returnDescription": "The layer extent in the map projection."
      },
      {
        "key": "getRandomColor",
        "description": "Generates a random colour, either as an HTML hex string (`\"#rrggbb\"`) or as an `[r, g, b]`\narray. Handy as a fallback colour when a legend value has no colour of its own.\n@param asArray {Boolean} `true` to get the colour as an `[r, g, b]` array (0-255 components) instead of a hex string.",
        "type": "function(asArray) : String|Array.<Number>",
        "qualifiedName": "ExtjsUtils.getRandomColor",
        "examples": [
          "var hex = ExtjsUtils.getRandomColor();      // \"#3fa2c1\"\nvar rgb = ExtjsUtils.getRandomColor(true);  // [63, 162, 193]"
        ],
        "returnDescription": "The generated colour: a `\"#rrggbb\"` string, or `[r, g, b]` when `asArray` is `true`."
      },
      {
        "key": "invertArray",
        "description": "Returns a new array with the elements of `arr` in reverse order. Note that the input array is\nconsumed in the process (its elements are `pop`ped one by one), so it is left empty\nafterwards — pass a copy (`arr.slice()`) when you still need the original.\n@param arr {Array} The array to reverse; emptied by the call.",
        "type": "function(arr) : Array",
        "qualifiedName": "ExtjsUtils.invertArray",
        "examples": [
          "var reversed = ExtjsUtils.invertArray(names.slice()); // 'names' is kept intact"
        ],
        "returnDescription": "A new array with the elements in reverse order."
      },
      {
        "key": "isLittleEndian",
        "description": "`true` when the machine is little-endian, `false` when big-endian. Computed once at load time\n(through a typed-array check); relevant when reading raw pixel/typed-array data.",
        "type": "Boolean",
        "qualifiedName": "ExtjsUtils.isLittleEndian",
        "examples": [
          "var byteOrder = ExtjsUtils.isLittleEndian ? \"little-endian\" : \"big-endian\";"
        ]
      },
      {
        "key": "isValidBBox",
        "description": "Tells whether a bounds object is usable: its width and height are numbers (not `NaN`), which\nis what a failed reprojection or a malformed capabilities bounding box produces.\n@param BBox {OpenLayers.Bounds} The bounds to validate.",
        "type": "function(BBox) : Boolean",
        "qualifiedName": "ExtjsUtils.isValidBBox",
        "examples": [
          "var bounds = ExtjsUtils.bboxTransform(layer.llbbox, undefined, app.mapPanel.map.getProjection());\nif (ExtjsUtils.isValidBBox(bounds)) app.mapPanel.map.zoomToExtent(bounds);"
        ],
        "returnDescription": "`true` when width and height are valid numbers, `false` otherwise."
      },
      {
        "key": "latinLowerString",
        "description": "Lower-cases a string and strips Latin accents (`á`→`a`, `ç`→`c`, `ã`→`a`, `æ`→`ae`, ...).\nUseful to compare or search user text and layer titles regardless of accents and case.\n@param s {String} The string to normalise.",
        "type": "function(s) : String",
        "qualifiedName": "ExtjsUtils.latinLowerString",
        "examples": [
          "ExtjsUtils.latinLowerString(\"Mata Atlântica\"); // \"mata atlantica\""
        ],
        "returnDescription": "The string in lower case without accented characters."
      },
      {
        "key": "layerBoundsIsVisible",
        "description": "Tells whether any part of a layer's maximum extent (`layer.getMaxExtent()`) intersects the\narea currently shown by the map.\n@param layer {OpenLayers.Layer} Layer to test.",
        "type": "function(layer) : Boolean",
        "qualifiedName": "ExtjsUtils.layerBoundsIsVisible",
        "examples": [
          "if (!ExtjsUtils.layerBoundsIsVisible(layer)) app.mapPanel.map.zoomToExtent(ExtjsUtils.LAYER.getLayerExtent(layer));"
        ],
        "returnDescription": "`true` when the layer extent intersects the current map view, `false` otherwise."
      },
      {
        "key": "removeDomListener",
        "description": "Removes a DOM event listener previously added with `addDomListener` (`removeEventListener` or\n`detachEvent`, whichever the browser supports). The callback must be the exact function\nreference that was registered.\n@param domObj {EventTarget} DOM object the listener was added to.\n@param evtName {String} DOM event name, without the `on` prefix.\n@param fn {function} The callback to remove (must be the same function passed to `addDomListener`).",
        "type": "function(domObj, evtName, fn)",
        "qualifiedName": "ExtjsUtils.removeDomListener",
        "examples": [
          "function onMove(evt) { console.log(evt.pageX, evt.pageY); }\nExtjsUtils.addDomListener(window, 'mousemove', onMove);\n// ...later\nExtjsUtils.removeDomListener(window, 'mousemove', onMove);"
        ]
      },
      {
        "key": "scrollIntoView",
        "description": "Scrolls an Ext container so that `item` becomes visible, keeping `padding` pixels between the\nitem and the top border of the scrollable area instead of leaving it glued to the edge.\nInspired by Ext's `Element.scrollIntoView`.\n@param container {Ext.Panel} Scrollable Ext container; its `body` element is the one scrolled.\n@param item {HTMLElement|Ext.Element|String} Element (or element id) inside the container to bring into view.\n@param padding {Number} Distance in pixels to keep between the item and the container's top edge.",
        "type": "function(container, item, padding)",
        "qualifiedName": "ExtjsUtils.scrollIntoView",
        "examples": [
          "ExtjsUtils.scrollIntoView(legendPanel, 'legend-entry-12', 20);"
        ]
      },
      {
        "key": "toBounds",
        "description": "Turns any of the usual extent shapes into an `OpenLayers.Bounds`, without reprojecting: an\n`OpenLayers.Bounds` comes back as is, a `[left, bottom, right, top]` array (`Bounds#toArray()`\norder, what offline area records and `postMessage` payloads carry) or a\n`{left, bottom, right, top}` object becomes a new one. Use it at the edge where an extent\narrives from outside (a message, a stored record) before calling map functions.\n@param extent {OpenLayers.Bounds|Array.<Number>|Object} The extent, in any of the three shapes.",
        "type": "function(extent) : OpenLayers.Bounds",
        "qualifiedName": "ExtjsUtils.toBounds",
        "examples": [
          "// an extent received from the parent page\nvar bounds = ExtjsUtils.toBounds(msg.message.extent); // [-5179428, -2460070, -5171880, -2455780]\nif (bounds) ExtjsUtils.ZOOM.zoomToExtent(bounds);"
        ],
        "returnDescription": "The bounds, or `null` when `extent` is missing or lacks a side."
      },
      {
        "key": "touchDispatchMouseEvents",
        "description": "Turns a single-touch event (native touch or HammerJS `pan`/`tap`) into the equivalent mouse\nevent — `touchstart`/`panstart`/`tap` → `mousedown`, `touchmove`/`panmove` → `mousemove`,\n`touchend`/`panend` → `mouseup` — and dispatches it on the touched element, so code written\nfor the mouse also reacts to touch. The original touch event is cancelled and stopped.\nMulti-touch gestures and events coming from a real mouse are left untouched.\n@param event {TouchEvent|Object} The touch (or HammerJS) event to convert.\n@param bubbleEvnt {Boolean} `true` to let the simulated mouse event bubble up to `window`, `false` to dispatch it on the target only.",
        "type": "function(event, bubbleEvnt)",
        "qualifiedName": "ExtjsUtils.touchDispatchMouseEvents",
        "examples": [
          "ExtjsUtils.addDomListener(panelEl, 'touchstart', function(evt) {\n  ExtjsUtils.touchDispatchMouseEvents(evt, true);\n});"
        ]
      },
      {
        "key": "unifyHammerEvent",
        "description": "Normalises a HammerJS (or native touch) event in place so it can be handled like an Ext/\nOpenLayers pointer event: fills `touches`/`changedTouches` from `pointers`/`changedPointers`,\nrenames `pinch*` types to `touch*`, sets `srcElement`, computes the page position `xy`\n(`[x, y]`, with an `equals` helper) and adds `getXY()` to the event and to its `srcEvent`.\n@param hammerJsEvt {Object} HammerJS event (or a native touch event) to normalise.",
        "type": "function(hammerJsEvt) : Object",
        "qualifiedName": "ExtjsUtils.unifyHammerEvent",
        "examples": [
          "hammer.on('panmove', function(evt) {\n  var xy = ExtjsUtils.unifyHammerEvent(evt).getXY(); // [pageX, pageY]\n});"
        ],
        "returnDescription": "The same event object, with the unified properties added."
      }
    ],
    "Alertify": [
      {
        "key": "_information",
        "completeAPI": "View the complete Alertify <a href='/api/#category_Alertify'>API here</a>.",
        "description": "Messages, alerts and questions for the reader. Usage: ExtjsUtils.ALERTIFY",
        "name": "ALERTIFY · messages and questions"
      },
      {
        "key": "DISABLE_ALERT_NAME",
        "description": "Name of the stored option (`COOKIE`) that disables the notifications shown by\n`Alertify.log`; the \"stop notifications\" checkbox of the notification writes it and\n`isEnabled`/`toggle` read and write it.",
        "type": "String",
        "qualifiedName": "ExtjsUtils.ALERTIFY.DISABLE_ALERT_NAME",
        "examples": [
          "var muted = !!ExtjsUtils.COOKIE.readOption(ExtjsUtils.ALERTIFY.DISABLE_ALERT_NAME);"
        ]
      },
      {
        "key": "SPAM",
        "description": "Spam filter used by `Alertify.log`: a message shown recently is not shown again until\nits spam time expires. Members: `isSpamMsg(msg)` returns true when `msg` was shown\nwithin its spam window; `addMsgToSpam(msg, time)` registers a message for `time`\nmilliseconds (`log` uses `config.spamTime`, default 10 s); `EXPIRE_DATE` maps the md5\nof each message to its expiry timestamp. Messages are keyed by md5, so the same text\ncounts as the same message. (`Alertify.alert` also calls it, but without a time, so\nalerts are effectively never muted.)",
        "type": "Object",
        "qualifiedName": "ExtjsUtils.ALERTIFY.SPAM",
        "examples": [
          "if (!ExtjsUtils.ALERTIFY.SPAM.isSpamMsg(msg)) {\n    alertify.log(msg);\n    ExtjsUtils.ALERTIFY.SPAM.addMsgToSpam(msg, 30000); // mute repeats for 30 s\n}"
        ]
      },
      {
        "key": "alert",
        "description": "Shows an user non blocking alert.\n\nPS: If layer is loading only the last message will be shown after load end.\n@param msg {String} Notification HTML content.",
        "type": "function(msg)",
        "qualifiedName": "ExtjsUtils.ALERTIFY.alert"
      },
      {
        "key": "confirmChoice",
        "description": "Modal for one or more labeled choices.\n\n- 1 choice: invokes callback immediately (no modal).\n- 2 choices: alertify.confirm with custom ok/cancel labels; both invoke onChoice.\n- 3+ choices: alertify.confirm with a &lt;select&gt;; OK confirms selection;\n  Cancel invokes config.onCancel when provided. Selecting an option auto-clicks OK\n  when config.autoConfirmOnSelect is not false (default true).\n@param html {String} Message HTML.\n@param choices {Array.<(String|{label:String, value: *})>} Option labels or {label,value} objects.\n@param onChoice {function} function(value, index, choice) for a picked option.\n@param config {Object} Optional: okLabel, cancelLabel, onCancel, autoConfirmOnSelect.",
        "type": "function(html, choices, onChoice, config)",
        "qualifiedName": "ExtjsUtils.ALERTIFY.confirmChoice"
      },
      {
        "key": "isEnabled",
        "description": "Tells whether notifications (`Alertify.log`) are enabled for this browser, i.e. the\nuser has not ticked \"stop notifications\" (stored under `DISABLE_ALERT_NAME`).",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.ALERTIFY.isEnabled",
        "examples": [
          "if (!ExtjsUtils.ALERTIFY.isEnabled()) ExtjsUtils.ALERTIFY.log(\"Important!\", {force: true});"
        ],
        "returnDescription": "True when notifications are enabled, false when the user disabled them."
      },
      {
        "key": "isMapLoaded",
        "description": "Tells whether the map has finished loading, i.e. whether `log` shows notifications\nimmediately instead of queueing them.",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.ALERTIFY.isMapLoaded",
        "examples": [
          "if (ExtjsUtils.ALERTIFY.isMapLoaded()) ExtjsUtils.ALERTIFY.log(\"Data refreshed.\");"
        ],
        "returnDescription": "True once `setMapLoaded` was called, false before."
      },
      {
        "key": "log",
        "description": "Shows an user notification (if enabled).\n\nPS: The messages before layer loading are delayed, when it finishes only the last message will be shown.\n@param msg {String} The message that will be displayed in the alert. It can be in HTML format.\n@param config {Object} Configuration parameters for the message.\n {\n    force: {Boolean} Force to show notification even when disabled.\n    func: {Callback} Callback function when the notification is clicked.\n    onlyMsg: {Boolean} True to only show message and hide the close and the stop notifications, False otherwise.\n    delay: {Numeric} Amout of time in milisseconds before the message hide.\n    spamTime: {Numeric} Time the same message to be shown again is considered spam. (even with force = true).\n }",
        "type": "function(msg, config)",
        "qualifiedName": "ExtjsUtils.ALERTIFY.log"
      },
      {
        "key": "removePageLoader",
        "description": "Fades out and removes the page loading overlay (`#loader-container`). Called by\n`setMapLoaded`; call it directly only when the platform's own loading flow is bypassed.",
        "type": "function()",
        "qualifiedName": "ExtjsUtils.ALERTIFY.removePageLoader",
        "examples": [
          "ExtjsUtils.ALERTIFY.removePageLoader();"
        ]
      },
      {
        "key": "setMapLoaded",
        "description": "Marks the map as loaded: flushes the notifications queued by `log` while the map was\nloading (only the last one is effectively visible, as one notification is shown at a\ntime) and removes the page loader. The platform calls it when the query finishes\nloading; query code does not normally need to.",
        "type": "function()",
        "qualifiedName": "ExtjsUtils.ALERTIFY.setMapLoaded",
        "examples": [
          "ExtjsUtils.ALERTIFY.setMapLoaded();"
        ]
      },
      {
        "key": "toggle",
        "description": "Enables or disables the notifications shown by `Alertify.log` for this browser (the\nsetting is persisted with `COOKIE`). Without an argument the current state is\ninverted. Messages logged with `{force: true}` are still shown when disabled.\n@param state {Boolean} True to enable, false to disable; omit to invert.",
        "type": "function(state)",
        "qualifiedName": "ExtjsUtils.ALERTIFY.toggle",
        "examples": [
          "ExtjsUtils.ALERTIFY.toggle(false); // silence the platform notifications in this query"
        ]
      }
    ],
    "ASSYNC": [
      {
        "key": "_information",
        "description": "Sequential async iteration helpers (map, filter, reduce awaiting each callback).\nUsage: ExtjsUtils.ASSYNC",
        "name": "ASSYNC · async loops"
      },
      {
        "key": "filter",
        "description": "`Array.filter` for async predicates, run sequentially: the predicate of each element\nis awaited before the next element is tested.\n@param arrayLike {Array} Array (or array-like) to iterate.\n@param asyncFunction {function} `async function(element, index)` resolving to a truthy value to keep the element.",
        "type": "function(arrayLike, asyncFunction) : Promise.<Array>",
        "qualifiedName": "ExtjsUtils.ASSYNC.filter",
        "examples": [
          "var valid = await ExtjsUtils.ASSYNC.filter(layers, async function(layer) {\n    return await layerHasData(layer);\n});"
        ],
        "returnDescription": "Resolves to the elements that passed the predicate, in order."
      },
      {
        "key": "map",
        "description": "`Array.map` for async callbacks, run **sequentially**: each element waits for the\nprevious callback's promise before the next one starts (unlike `Promise.all(map(...))`,\nwhich starts them all at once). Use it to process items one at a time, e.g. a request\nper element.\n@param arrayLike {Array} Array (or array-like) to iterate.\n@param asyncFunction {function} `async function(element, index)` returning the mapped value (or a promise of it).",
        "type": "function(arrayLike, asyncFunction) : Promise.<Array>",
        "qualifiedName": "ExtjsUtils.ASSYNC.map",
        "examples": [
          "var areas = await ExtjsUtils.ASSYNC.map(features, async function(f) {\n    return await fetchAreaFor(f.attributes.code);\n});"
        ],
        "returnDescription": "Resolves to the mapped values, in order."
      },
      {
        "key": "reduce",
        "description": "`Array.reduce` for async reducers, run sequentially: each call receives the awaited\nresult of the previous one.\n@param arrayLike {Array} Array (or array-like) to iterate.\n@param asyncFunction {function} `async function(accumulator, element, index)` returning the next accumulator.\n@param defaultVal {*} Initial accumulator value.",
        "type": "function(arrayLike, asyncFunction, defaultVal) : Promise.<*>",
        "qualifiedName": "ExtjsUtils.ASSYNC.reduce",
        "examples": [
          "var total = await ExtjsUtils.ASSYNC.reduce(codes, async function(sum, code) {\n    return sum + await fetchAreaFor(code);\n}, 0);"
        ],
        "returnDescription": "Resolves to the final accumulator."
      }
    ],
    "CHECK": [
      {
        "key": "_information",
        "description": "Type and environment checks (mobile, browser support, value types).\nUsage: ExtjsUtils.CHECK",
        "name": "CHECK · type and browser checks"
      },
      {
        "key": "browserSupported",
        "description": "Tells whether the browser has everything the map calculations need: Web Workers,\ntyped arrays (`Uint8Array`) and a WebGL context.",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.browserSupported",
        "examples": [
          "if (!ExtjsUtils.CHECK.browserSupported()) ExtjsUtils.ALERTIFY.alert(\"Your browser cannot run this map.\");"
        ],
        "returnDescription": "True on a supported browser, false otherwise."
      },
      {
        "key": "hasWebWorker",
        "description": "Tells whether the browser supports Web Workers.",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.hasWebWorker",
        "examples": [
          "if (!ExtjsUtils.CHECK.hasWebWorker()) ExtjsUtils.ALERTIFY.log(\"Calculations will run in the main thread.\");"
        ],
        "returnDescription": "True when `window.Worker` exists, false otherwise."
      },
      {
        "key": "isArray",
        "description": "Tells whether a value is an array (`Ext.isArray`).\n@param arg {*} Value to check.",
        "type": "function(arg) : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.isArray",
        "examples": [
          "var list = ExtjsUtils.CHECK.isArray(value) ? value : [value];"
        ],
        "returnDescription": "True when `arg` is an array, false otherwise."
      },
      {
        "key": "isChrome",
        "description": "Tells whether the browser is Google Chrome (`Ext.isChrome`).",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.isChrome",
        "examples": [
          "if (ExtjsUtils.CHECK.isChrome()) enableWebGLExtras();"
        ],
        "returnDescription": "True on Chrome, false otherwise."
      },
      {
        "key": "isColor",
        "description": "Tells whether a string is a valid CSS colour (`#rgb`, `rgb(...)`, a colour name, ...),\nusing the browser's own parser.\n@param arg {String} Colour to validate.",
        "type": "function(arg) : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.isColor",
        "examples": [
          "ExtjsUtils.CHECK.isColor(\"#ff8800\"); // true"
        ],
        "returnDescription": "True when the browser accepts the string as a colour."
      },
      {
        "key": "isFunction",
        "description": "Tells whether a value is a function.\n@param obj {*} Value to check.",
        "type": "function(obj) : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.isFunction",
        "examples": [
          "if (ExtjsUtils.CHECK.isFunction(config.onClick)) config.onClick(feature);"
        ],
        "returnDescription": "True when `obj` is a function, false otherwise."
      },
      {
        "key": "isFunctionString",
        "description": "Tells whether a string holds JavaScript source that evaluates to a function (e.g.\n`\"function(a) { return a; }\"` or `\"a => a\"`), as stored in serialized layer definitions.\nThe string is evaluated with `new Function`, so only pass trusted text.\n@param str {String} Source text to test.",
        "type": "function(str) : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.isFunctionString",
        "examples": [
          "ExtjsUtils.CHECK.isFunctionString(\"function(inputs) { return 1; }\"); // true"
        ],
        "returnDescription": "True when the text evaluates to a function, false otherwise (including syntax errors)."
      },
      {
        "key": "isMobile",
        "description": "Device-based mobile test: true when the browser exposes `window.orientation` (phones\nand tablets), optionally restricted to small screens. It does not change when the\nwindow is resized. This is different from `MOBILE_UTILS.isMobile()`, which follows the\nresponsive CSS media query (viewport narrower than 768px) and therefore tells whether\nthe mobile layout is currently active — use that one for layout decisions.\nIdea taken from Leaflet's Browser.js.\n@param strict {Boolean} True to also require a small screen (`screen.width + screen.height < 800`).",
        "type": "function(strict) : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.isMobile",
        "examples": [
          "var touchDevice = ExtjsUtils.CHECK.isMobile();\nvar mobileLayout = MOBILE_UTILS.isMobile();"
        ],
        "returnDescription": "True on a mobile device, false otherwise."
      },
      {
        "key": "isObject",
        "description": "Tells whether a value is of type `\"object\"` (plain objects, arrays, dates...). `null`\nalso has type `\"object\"` in JavaScript, so it is rejected unless `acceptNullAsObject`\nis true.\n@param obj {*} Value to check.\n@param acceptNullAsObject {Boolean} True to also accept `null`.",
        "type": "function(obj, acceptNullAsObject) : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.isObject",
        "examples": [
          "if (ExtjsUtils.CHECK.isObject(config)) Ext.apply(defaults, config);"
        ],
        "returnDescription": "True when `obj` is an object (and not null, unless accepted)."
      },
      {
        "key": "isOlderBrowser",
        "description": "Tells whether the browser is an old one (roughly before 2014) by checking for\n`Element.prototype.remove`. Returns false on Chrome 23+, Edge 12+, Firefox 23+,\nOpera 15+ and Safari 7+; a false result does not guarantee full support (see\n`browserSupported`).",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.isOlderBrowser",
        "examples": [
          "if (ExtjsUtils.CHECK.isOlderBrowser()) ExtjsUtils.ALERTIFY.alert(\"Please update your browser.\");"
        ],
        "returnDescription": "True on an old browser, false otherwise."
      },
      {
        "key": "isString",
        "description": "Tells whether a value is a string (primitive or `String` object).\n@param arg {*} Value to check.",
        "type": "function(arg) : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.isString",
        "examples": [
          "var col = ExtjsUtils.CHECK.isString(column) ? csv.columnNameToInd(column) : column;"
        ],
        "returnDescription": "True when `arg` is a string, false otherwise."
      },
      {
        "key": "isWebkit",
        "description": "Tells whether the browser is WebKit based (Chrome, Safari...; `Ext.isWebKit`).",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.isWebkit",
        "examples": [
          "var cssPrefix = ExtjsUtils.CHECK.isWebkit() ? \"-webkit-\" : \"\";"
        ],
        "returnDescription": "True on a WebKit browser, false otherwise."
      },
      {
        "key": "msieversion",
        "description": "Tells whether the browser is Internet Explorer (any version, including IE 11).",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.CHECK.msieversion",
        "examples": [
          "if (ExtjsUtils.CHECK.msieversion()) ExtjsUtils.ALERTIFY.alert(\"Please use a modern browser.\");"
        ],
        "returnDescription": "Truthy on Internet Explorer, false otherwise."
      }
    ],
    "COMPONENTS": [
      {
        "key": "_information",
        "description": "Builders of the buttons of a layer's row (associated buttons). Usage: ExtjsUtils.COMPONENTS",
        "name": "COMPONENTS · row button builders"
      },
      {
        "key": "createAssociatedButton",
        "description": "Creates an `Ext.Button` that mirrors another button or checkbox of the interface — the\n\"associated button\" behind `paramsButtonConfig` entries with an `associatedButtonID`. The\noriginal component is looked up by id (`btnConfig.associatedButtonID`, a string or a\nfunction returning it) after render; when it is a toggle button or a checkbox the new\nbutton becomes a toggle kept in sync both ways, otherwise clicking the new button triggers\nthe original's `handler`/click. Your own `toggleHandler`/`handler` in `btnConfig` replace\nthe syncing ones. The platform calls this for the legend window; a query may call it to\nplace such a button in its own HTML.\n@param btnConfig {Object} `Ext.Button` config: `associatedButtonID` plus any button option (`text`, `iconCls`, `tooltip`, `pressed`, `enableToggle`, `handler`, `toggleHandler`...). Its values win over `defaultProperties`.\n@param defaultProperties {Object} Defaults applied before `btnConfig` (typically `pressed`, `toggleGroup`, `cls`, `iconCls`); any key present in `btnConfig` overrides them.",
        "type": "function(btnConfig, defaultProperties) : Ext.Button",
        "qualifiedName": "ExtjsUtils.COMPONENTS.createAssociatedButton",
        "examples": [
          "var mirror = ExtjsUtils.COMPONENTS.createAssociatedButton({\n  associatedButtonID: 'property_pick',   // id of an existing toggle button\n  text: 'Pick a point'\n}, { iconCls: 'gxp-icon-getfeatureinfo' });\nmirror.render('my-toolbar-div');"
        ],
        "returnDescription": "The new button, not yet rendered; add it to a container or call `render`."
      }
    ],
    "COOKIE": [
      {
        "key": "_information",
        "description": "Persist small user options across visits (localStorage, with a cookie fallback).\nUsage: ExtjsUtils.COOKIE",
        "name": "COOKIE · saved user options"
      },
      {
        "key": "INTRO_START",
        "description": "Reserved option name (`\"mp_intro\"`) for remembering that the intro tour was already\nshown in this browser. The platform does not set it by itself; use it from query code\nto show a tour only once.",
        "type": "String",
        "qualifiedName": "ExtjsUtils.COOKIE.INTRO_START",
        "examples": [
          "if (!ExtjsUtils.COOKIE.readOption(ExtjsUtils.COOKIE.INTRO_START)) {\n    ExtjsUtils.INTROJS.loadAndRun();\n    ExtjsUtils.COOKIE.saveOption(ExtjsUtils.COOKIE.INTRO_START, 1);\n}"
        ]
      },
      {
        "key": "NAME",
        "description": "Store name derived from the page path (`\"MAPSERVER_\" + pathname without slashes`), a\nready-made prefix for options that must not be shared between pages of the same host.\nThe platform itself does not read it: options are stored under the names given to\n`saveOption`.",
        "type": "String",
        "qualifiedName": "ExtjsUtils.COOKIE.NAME",
        "examples": [
          "ExtjsUtils.COOKIE.saveOption(ExtjsUtils.COOKIE.NAME + \"_seen_tip\", 1);"
        ]
      },
      {
        "key": "TRY_HTTPS",
        "description": "Option name (`\"mp_no_https\"`) under which `REQUEST.tryHttps` records that the HTTPS\nredirect was already attempted in this browser.",
        "type": "String",
        "qualifiedName": "ExtjsUtils.COOKIE.TRY_HTTPS"
      },
      {
        "key": "deleteOption",
        "description": "Removes a stored option. Fires `COOKIE.events` `change` (with `value: null`) when\nthere was a non-empty value to remove.\n@param cname {String} Name of the option to remove.",
        "type": "function(cname)",
        "qualifiedName": "ExtjsUtils.COOKIE.deleteOption",
        "examples": [
          "ExtjsUtils.COOKIE.deleteOption(\"myquery_hide_welcome\");"
        ]
      },
      {
        "key": "events",
        "description": "Observable that fires `change` after an option is saved or deleted. The listener\nreceives `{type: 'save', name, value}` (`value` is null on delete). Use it to react\nto settings changed elsewhere in the interface, e.g. the notifications toggle.",
        "type": "Ext.util.Observable",
        "qualifiedName": "ExtjsUtils.COOKIE.events",
        "examples": [
          "ExtjsUtils.COOKIE.events.on(\"change\", function(evt) {\n    if (evt.name === ExtjsUtils.ALERTIFY.DISABLE_ALERT_NAME) console.log(\"alerts toggled\");\n});"
        ]
      },
      {
        "key": "readOption",
        "description": "Reads a stored option. Values always come back as strings: a \"false\" saved through\n`saveOption` is the empty string `\"\"` and a \"true\" is `\"1\"`, so `if (readOption(name))`\nworks; avoid storing a literal `\"0\"`, which is truthy as a string.\n@param cname {String} Name of the option to read.",
        "type": "function(cname) : String|null",
        "qualifiedName": "ExtjsUtils.COOKIE.readOption",
        "examples": [
          "if (!ExtjsUtils.COOKIE.readOption(\"myquery_hide_welcome\")) {\n    ExtjsUtils.ALERTIFY.log(\"Welcome! Click a municipality to see its fire history.\");\n}"
        ],
        "returnDescription": "The stored string, or null/undefined when the option was never saved."
      },
      {
        "key": "saveOption",
        "description": "Persists a user option in the browser (localStorage) under `cname`, replacing any\nprevious value, and fires `COOKIE.events` `change` when the value actually changed.\nBooleans are normalized: `true`/`1` are stored as `\"1\"` and `false`/`0` as `\"\"`, so\n`readOption` can be used directly in a condition. `null`/`undefined` are not allowed.\nTypical use: \"don't show this again\" preferences.\n@param cname {String} Option name.\n@param cvalue {String|Number|Boolean} Value to store (kept as a string).",
        "type": "function(cname, cvalue)",
        "qualifiedName": "ExtjsUtils.COOKIE.saveOption",
        "examples": [
          "ExtjsUtils.COOKIE.saveOption(\"myquery_hide_welcome\", true);"
        ]
      }
    ],
    "COORDINATE": [
      {
        "key": "_information",
        "description": "Coordinate helpers: mouse/pixel positions to longitude/latitude and polygon edge equations.\nUsage: ExtjsUtils.COORDINATE",
        "name": "COORDINATE · mouse position to lon/lat"
      },
      {
        "key": "getLatLong",
        "description": "Converts a pixel position on the map viewport into a map coordinate (`OpenLayers.LonLat`).\nAccepts either an OpenLayers/Ext mouse event (its `xy` pixel is used) or a plain `{x, y}`\npixel object. The result is in the map projection; pass `fromProj` to get it transformed\ninto that projection instead (e.g. `\"EPSG:4326\"` for longitude/latitude).\n@param xy {MouseEvent|Object} A mouse event carrying an `xy` pixel, or a `{x, y}` pixel position relative to the map viewport.\n@param fromProj {String|OpenLayers.Projection} Projection to transform the result into; omit to keep the map projection.",
        "type": "function(xy, fromProj) : OpenLayers.LonLat",
        "qualifiedName": "ExtjsUtils.COORDINATE.getLatLong",
        "examples": [
          "// Longitude/latitude under the mouse, from an OpenLayers click event\napp.mapPanel.map.events.register('click', null, function(evt) {\n  var lonLat = ExtjsUtils.COORDINATE.getLatLong(evt, \"EPSG:4326\");\n  console.log(lonLat.lon, lonLat.lat);\n});"
        ],
        "returnDescription": "The map coordinate under the pixel."
      },
      {
        "key": "getLineEquations",
        "description": "Computes the line equation of every edge of a polygon feature (each consecutive pair of\nvertices, closing back to the first). Each edge is described as `y = A*x + B` by an object\n`{A, B, x, y}` — `A` the slope, `B` the intercept, `x` and `y` the `[start, end]`\ncoordinates of the segment. The returned array also carries two helpers:\n`getLineX(y, edge)` and `getLineY(x, edge)`, which solve the equation for one edge and\nhandle vertical (`A === Infinity`) and horizontal (`A === 0`) segments. Used by the\npolygon rasterisation that finds the pixels of a layer inside a drawn area.\n@param polygonFeature {OpenLayers.Feature.Vector} Feature whose polygon geometry is processed.",
        "type": "function(polygonFeature) : Array.<Object>",
        "qualifiedName": "ExtjsUtils.COORDINATE.getLineEquations",
        "examples": [
          "var edges = ExtjsUtils.COORDINATE.getLineEquations(drawnFeature);\nvar xAtY = edges.getLineX(-2300000, edges[0]); // x where the first edge crosses that y"
        ],
        "returnDescription": "One `{A, B, x, y}` object per edge, plus the `getLineX`/`getLineY` helper functions attached to the array."
      }
    ],
    "CSS": [
      {
        "key": "_information",
        "description": "Inject, replace and remove CSS rules from a query. Rules defined here are removed when another query loads, so the customization never leaks between queries.\nUsage: ExtjsUtils.CSS",
        "name": "CSS · query stylesheet rules"
      },
      {
        "key": "defineClass",
        "description": "Adds a CSS rule to the query's dynamic stylesheet — the main way a query restyles the\ninterface (hide the top bar, recolour the legend, style its own HTML). The selector is any\nCSS selector and doubles as the rule's unique key: defining the same selector again\nreplaces the previous rule. The rule is tracked and removed when another query loads, so\nthe styling never leaks between queries; the page's static CSS is only overridden, never\nchanged. Does nothing while the query is only being evaluated (`justEval` mode).\n`QUERY.decorate` forwards every key that is not `header`, `footer` or `run` to this\nfunction, so `decorate({ '#topbar': 'display: none;' })` is the same call.\n@param selector {String} CSS selector the rule applies to, e.g. `'#topbar'` or `'.legend-title, .legend-value'`. Treated as an opaque key: no lookup is performed.\n@param rules {String|Object} The declarations only (no braces): a string such as `\"background-color: green; color: white;\"`, or an object `{ 'background-color': 'green', color: 'white' }`.",
        "type": "function(selector, rules)",
        "qualifiedName": "ExtjsUtils.CSS.defineClass",
        "examples": [
          "ExtjsUtils.CSS.defineClass('#topbar', 'background-color: #1a4d2e;');\nExtjsUtils.CSS.defineClass('.my-popup', { position: 'absolute', 'z-index': 10000, padding: '8px' });"
        ]
      },
      {
        "key": "hasClass",
        "description": "Tells whether a rule with exactly this selector exists in the query's dynamic stylesheet\n(i.e. was added with `defineClass` and not removed). The selector is compared as a whole\nstring; a comma-separated selector list is not split.\n@param selector {String} The selector used in `defineClass`.",
        "type": "function(selector) : Boolean",
        "qualifiedName": "ExtjsUtils.CSS.hasClass",
        "examples": [
          "if (!ExtjsUtils.CSS.hasClass('#topbar')) ExtjsUtils.CSS.defineClass('#topbar', 'display: none;');"
        ],
        "returnDescription": "`true` when the rule exists, `false` otherwise."
      },
      {
        "key": "importExternalCss",
        "description": "Loads an external stylesheet by appending a `<link rel=\"stylesheet\">` to the page head —\nfor instance a font or an icon set the query's HTML needs. By default the link is tagged\nso it is removed when another query loads (like the rules of `defineClass`); pass\n`shouldClear = false` to keep it. Does nothing while the query is only being evaluated.\n@param cssFile {String} URL of the CSS file.\n@param id {String} `id` to give the generated `<link>` element.\n@param shouldClear {Boolean} `true` to remove the stylesheet when another query loads; `false` to keep it permanently.",
        "type": "function(cssFile, id, shouldClear)",
        "qualifiedName": "ExtjsUtils.CSS.importExternalCss",
        "examples": [
          "ExtjsUtils.CSS.importExternalCss('https://fonts.googleapis.com/css?family=Roboto', 'roboto-font');"
        ]
      },
      {
        "key": "removeAllClasses",
        "description": "Removes every dynamic CSS rule (`defineClass`) and every stylesheet imported with\n`importExternalCss` that was left removable. The platform calls it when another query\nloads (`QUERY.clearQueryGlobalProperties`); a query may call it to reset its own styling.",
        "type": "function()",
        "qualifiedName": "ExtjsUtils.CSS.removeAllClasses",
        "examples": [
          "ExtjsUtils.CSS.removeAllClasses();"
        ]
      },
      {
        "key": "removeClass",
        "description": "Removes the rule(s) with exactly this selector from the query's dynamic stylesheet, undoing\na `defineClass`. The selector is the rule's key (compared as a whole string). Only dynamic\nrules are affected — the page's static CSS is never changed. Does nothing when the selector\nis not defined.\n@param selector {String} The selector used in `defineClass`.",
        "type": "function(selector)",
        "qualifiedName": "ExtjsUtils.CSS.removeClass",
        "examples": [
          "ExtjsUtils.CSS.defineClass('#topbar', 'display: none;');\n// ...later, show the top bar again\nExtjsUtils.CSS.removeClass('#topbar');"
        ]
      },
      {
        "key": "runAnimation",
        "description": "Starts a CSS animation on an element by adding the class that defines it, then forces a\nredraw so the animation actually plays, and runs an optional callback right after it\nstarts (about 10 ms later). The class stays on the element; running different animations\nwith the same class gives unexpected results.\n@param elm {Ext.Element} Element to animate (an `Ext.Element`, e.g. `Ext.get('id')`).\n@param animationCls {String} CSS class whose rules define the animation.\n@param callback {function} Called once the animation has started.\n@param scope {Object} `this` for the callback.",
        "type": "function(elm, animationCls, callback, scope)",
        "qualifiedName": "ExtjsUtils.CSS.runAnimation",
        "examples": [
          "ExtjsUtils.CSS.defineClass('.blink', 'animation: blink 1s 3;');\nExtjsUtils.CSS.runAnimation(Ext.get('legend-entry-3'), 'blink');"
        ]
      }
    ],
    "CSV": [
      {
        "key": "_information",
        "description": "CSV parsing and joining helpers. The table object returned by the loadcsv tool is documented under Tools > LoadCsv.\nUsage: ExtjsUtils.CSV",
        "name": "CSV · read and join tables"
      },
      {
        "key": "CsvTable",
        "description": "Constructor of the table object that wraps a parsed CSV matrix (header row first) and\noffers lookups by column name, filtering and indexing: `new ExtjsUtils.CSV.CsvTable(matrix)`.\nThe `{{loadcsv|id=x|url=...}}` widget returns one of these at `inputs.id[\"x\"]`; its\nmethods (`getLines`, `getValue`, `columnNameToInd`, `createIndexes`, `getColunsInd`, ...)\nare documented under Tools > LoadCsv. Build one yourself to wrap a CSV you fetched or\njoined with `CSV.joinCSV`.\n@param matrix {Array.<Array.<String>>} Rows of the CSV, the first row being the header (e.g. the output of `ParseCSV`).",
        "type": "function(matrix)",
        "qualifiedName": "ExtjsUtils.CSV.CsvTable",
        "examples": [
          "var table = new ExtjsUtils.CSV.CsvTable(ParseCSV(xhr.responseText));\nvar iYear = table.columnNameToInd(\"Year\");\nvar rows2024 = table.getLines([iYear], [2024]);"
        ]
      },
      {
        "key": "joinCSV",
        "description": "Joins two CSV matrices (arrays of rows) on the values of the given key columns: every\nrow of A is kept, in order, followed by the columns of its matching B row minus the\nkey columns of B. Example: A `[\"title\", \"value\", \"index\"]` and B\n`[\"Total\", \"Category\", \"Data\"]` joined with `[2], [1]` give\n`[\"title\", \"value\", \"index\", \"Total\", \"Data\"]`. Rows of A without a match are not\npadded reliably (they receive the cells of the previously matched row, or a single\nundefined cell for the first row), so make sure every key of A exists in B — header\nrows included, when both matrices carry one. Wrap the result in\n`new ExtjsUtils.CSV.CsvTable(...)` to query it.\n@param csvA {Array.<Array.<String>>} Rows of table A (header included when both tables have one).\n@param csvB {Array.<Array.<String>>} Rows of table B.\n@param columnsA {Array.<Number>} Indexes of the key columns of A.\n@param columnsB {Array.<Number>} Indexes of the matching key columns of B (same order as `columnsA`).",
        "type": "function(csvA, csvB, columnsA, columnsB) : Array.<Array.<String>>",
        "qualifiedName": "ExtjsUtils.CSV.joinCSV",
        "examples": [
          "var legend = inputs.id[\"legend_csv\"].getLines(undefined, undefined, true); // with header\nvar areas = inputs.id[\"area_csv\"].getLines(undefined, undefined, true);\nvar joined = new ExtjsUtils.CSV.CsvTable(ExtjsUtils.CSV.joinCSV(legend, areas, [0], [1]));"
        ],
        "returnDescription": "The joined rows."
      },
      {
        "key": "readMapCsvToMatrix",
        "description": "Parses a \"category, value\" CSV produced by a cross-tabulation of maps into a nested\nnumeric matrix. Each pair of digits of the category code is one (1-based) map class\nindex, read from the right: `201` is `[02, 01]`, `1001` is `[10, 01]`, so the value of\nline `201, 166311249` ends up at `matrix[0][1]` (class 1 of the last map, class 2 of the\nprevious one). The header line is skipped. CSV format:\n\n    Category, Value\n    201, 166311249\n    205, 12311\n    1001, 83696\n@param csvContent {String} Raw CSV text.\n@param qntMaps {Number} Number of maps (digit pairs) in the category code; when omitted, the number of non-empty columns of the header line.",
        "type": "function(csvContent, qntMaps) : Array.<Array.<Number>>",
        "qualifiedName": "ExtjsUtils.CSV.readMapCsvToMatrix",
        "examples": [
          "var matrix = ExtjsUtils.CSV.readMapCsvToMatrix(xhr.responseText, 2);\nvar area = matrix[0][1]; // value of category \"0201\" (last map class 1, first map class 2)"
        ],
        "returnDescription": "Nested matrix indexed by the class of each map (0-based)."
      }
    ],
    "DOMHelper": [
      {
        "key": "_information",
        "description": "DOM element helpers.\nUsage: ExtjsUtils.DOMHelper",
        "name": "DOMHelper · page elements"
      },
      {
        "key": "isDescendant",
        "description": "Tells whether the DOM element `child` is inside `parent` (at any depth). Typical use: in a\ntimer that auto-closes a floating popup, check whether the element last hovered by the mouse\nis still part of the popup before hiding it.\n@param parent {HTMLElement} Candidate ancestor element.\n@param child {HTMLElement} Element to test.",
        "type": "function(parent, child) : Boolean",
        "qualifiedName": "ExtjsUtils.DOMHelper.isDescendant",
        "examples": [
          "var popup = document.getElementById('info-popup');\nif (!ExtjsUtils.DOMHelper.isDescendant(popup, window.lastHoveredElement)) {\n  popup.style.display = 'none';\n}"
        ],
        "returnDescription": "`true` when `child` is contained in `parent`, `false` otherwise."
      },
      {
        "key": "removeElement",
        "description": "Removes an element from the DOM together with all its children (old IE left the children\nbehind). Accepts a plain DOM element or an `Ext.Element` (its `dom` is used). Does nothing\nwhen the element is not attached to the document.\n@param element {HTMLElement|Ext.Element} Element to remove completely.",
        "type": "function(element)",
        "qualifiedName": "ExtjsUtils.DOMHelper.removeElement",
        "examples": [
          "ExtjsUtils.DOMHelper.removeElement(document.getElementById('my-popup'));"
        ]
      }
    ],
    "EVENTS": [
      {
        "key": "_information",
        "description": "Helpers to bind OpenLayers events to the lifecycle of Ext components.\nUsage: ExtjsUtils.EVENTS",
        "name": "EVENTS · event lifecycles"
      },
      {
        "key": "monExtOL",
        "description": "Registers a listener on an OpenLayers object (map, layer, control...) that is automatically\nunregistered when the given Ext component is destroyed — the OpenLayers counterpart of\nExt's `mon`, which does not work with OpenLayers event objects. Use it when a window or\npanel you create reacts to map events, so the handler does not outlive the component.\n@param extObject {Ext.Component} Ext component whose `destroy` event removes the listener.\n@param openlayersObj {Object} OpenLayers object exposing `events` (e.g. the map or a layer).\n@param evtNameOL {String} OpenLayers event name (e.g. `'moveend'`, `'visibilitychanged'`).\n@param callback {function} Listener called with the OpenLayers event.\n@param scope {Object} `this` for the callback.",
        "type": "function(extObject, openlayersObj, evtNameOL, callback, scope)",
        "qualifiedName": "ExtjsUtils.EVENTS.monExtOL",
        "examples": [
          "var win = new Ext.Window({ title: 'Map info', html: '' });\nExtjsUtils.EVENTS.monExtOL(win, app.mapPanel.map, 'moveend', function() {\n  win.body.update('Zoom: ' + app.mapPanel.map.getZoom());\n});"
        ]
      }
    ],
    "FEATURE": [
      {
        "key": "_information",
        "description": "Helpers to build OpenLayers vector features for drawable/vector layers.\nUsage: ExtjsUtils.FEATURE",
        "name": "FEATURE · build vector features"
      },
      {
        "key": "fromGeometry",
        "description": "Build a Vector from a geometry, taking user attributes from ``sourceFeature``.\nOptionally copies OpenLayers ``id`` / ``fid`` so list↔map identity survives rebuilds.\n@param geometry {OpenLayers.Geometry} Geometry of the new feature.\n@param attrSource {OpenLayers.Feature.Vector} Attr (and id) source; omit for a brand-new feature.\n@param options {Object} Optional behaviour switches.\n@param options.attributes {Object} Explicit attrs (skips cloning from source).\n@param options.copyId {Boolean} Copy id/fid when ``sourceFeature`` is set.",
        "type": "function(geometry, attrSource, options) : OpenLayers.Feature.Vector",
        "qualifiedName": "ExtjsUtils.FEATURE.fromGeometry",
        "examples": [
          "layer.addFeatures([ExtjsUtils.FEATURE.fromGeometry(newGeom, oldFeature)]);\n  ExtjsUtils.FEATURE.fromGeometry(newGeom, oldFeature, { copyId: false });"
        ],
        "returnDescription": "The new feature."
      }
    ],
    "GEOJSON": [
      {
        "key": "_information",
        "description": "Functions to handle GEOJSON transformations.",
        "name": "GEOJSON · GeoJSON and shapefiles"
      },
      {
        "key": "geojson2Features",
        "description": "Parse Geojson to Layer.Feature.\n\nPS: fromProj is resolved by `GEOJSON.getGeojsonProjection` (GeoJSON `crs`, then\n    the query/URL `defaultFromProj`, then `CONFIGURATION.DEFAULT_FROM_PROJ`).\nPS2: toProj defaults to the map projection (EPSG:900913).\nPS3: CRS outside EPSG:4326 / EPSG:3857 (e.g. SIRGAS EPSG:4674) requires\n     PROJECTION.setProjectionOptions().\n@param geojson {Object|Array.<Object>} Geojson object or array of objects.\n@param fromProj {String} Original projection of Geojson object.\n@param toProj {String} To projection of Geojson object.",
        "type": "function(geojson, fromProj, toProj) : Array.<OpenLayers.Feature>",
        "qualifiedName": "ExtjsUtils.GEOJSON.geojson2Features",
        "returnDescription": "Returns the array of parsed Features."
      },
      {
        "key": "getGeojsonProjection",
        "description": "Resolve source projection from a parsed GeoJSON object.\nOpenLayers.Format.GeoJSON.read does not read crs; geojson2Features only\nconsults crs when fromProj is omitted. Checks top-level crs, then defaultFromProj,\nthen the query/URL `defaultFromProj` setting, then\n`CONFIGURATION.DEFAULT_FROM_PROJ` (EPSG:900913) unless disabled.\n@param geojson {Object} Parsed GeoJSON\n@param defaultFromProj {String} Fallback when crs is absent",
        "type": "function(geojson, defaultFromProj) : String|null",
        "qualifiedName": "ExtjsUtils.GEOJSON.getGeojsonProjection",
        "returnDescription": "EPSG code, or null when the default is disabled."
      },
      {
        "key": "shapefile2GeojsonAsync",
        "description": "Parse shapefile to compatible Layer features.\nDBF file is optional.\n\nPS:ParamOptions\n  {\n  callback: {Function} called when the shapefile is parsed, as function(geojson, data) with `this` = paramOptions.layer (geojson: the parsed GeoJSON; data: the whole parse result),\n  fromProj: {String?} 'Projection name.' (EPSG:4326 or EPSG:900913),\n  dbf: (Optional?)'DBF file to load along with shapefile.',\n  limitCount: (Optional?)'Limit the amount of processed geometries'\n  }\n@param shpFile {File} Shapefile content (Needs to be in projection EPSG:4326 or EPSG:900913.\n@param paramOptions {ParamOptions} Additional configuration for handling shapefile.\nParamOptions\n  {\n  callback: {Function} called when the shapefile is parsed, as function(geojson, data) with `this` = paramOptions.layer (geojson: the parsed GeoJSON; data: the whole parse result),\n  fromProj: {String?} 'Projection name.' (EPSG:4326 or EPSG:900913),\n  dbf: (Optional?)'DBF file to load along with shapefile.',\n  limitCount: (Optional?)'Limit the amount of processed geometries'\n  }",
        "type": "function(shpFile, paramOptions)",
        "qualifiedName": "ExtjsUtils.GEOJSON.shapefile2GeojsonAsync"
      }
    ],
    "GEOMETRY": [
      {
        "key": "_information",
        "description": "JSTS-backed geometry operations (union, intersection, validation) for OpenLayers 2.x geometries. Every operation accepts OpenLayers geometries, converts them to JTS, runs the JTS method and converts the result back. Usage: ExtjsUtils.GEOMETRY.&lt;operation&gt;(geometryA, geometryB); the JSTS library is loaded on demand by loadGeometryLibrary.",
        "name": "GEOMETRY · union, intersection, validation"
      },
      {
        "key": "contains",
        "description": "Tells whether `aGeom` contains `bGeom` entirely (no point of `bGeom` lies outside\n`aGeom`, and their interiors share at least one point). A polygon does not `contain` a\npoint on its own boundary — use `covers` for that. Both geometries are required.\nRequires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Container geometry.\n@param bGeom {OpenLayers.Geometry} Geometry to test.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.contains",
        "examples": [
          "var inside = ExtjsUtils.GEOMETRY.contains(municipality.geometry, clickedPoint);"
        ],
        "returnDescription": "True when `aGeom` contains `bGeom`."
      },
      {
        "key": "convexHull",
        "description": "Smallest convex polygon containing the geometry. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : OpenLayers.Geometry",
        "qualifiedName": "ExtjsUtils.GEOMETRY.convexHull",
        "examples": [
          "var hull = ExtjsUtils.GEOMETRY.convexHull(multiPoint);"
        ],
        "returnDescription": "The convex hull (a `Polygon`; a `Point`/`LineString` for degenerate input)."
      },
      {
        "key": "coveredBy",
        "description": "Tells whether `aGeom` is covered by `bGeom` (every point of `aGeom` lies inside or on\nthe boundary of `bGeom`); the inverse of `covers`. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry to test.\n@param bGeom {OpenLayers.Geometry} Covering geometry.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.coveredBy",
        "examples": [
          "var fullyInside = ExtjsUtils.GEOMETRY.coveredBy(property.geometry, biome.geometry);"
        ],
        "returnDescription": "True when `bGeom` covers `aGeom`."
      },
      {
        "key": "covers",
        "description": "Tells whether `aGeom` covers `bGeom`: every point of `bGeom` lies inside or on the\nboundary of `aGeom`. Like `contains`, but true as well for a point on the boundary.\nRequires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Covering geometry.\n@param bGeom {OpenLayers.Geometry} Geometry to test.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.covers",
        "examples": [
          "var onOrInside = ExtjsUtils.GEOMETRY.covers(municipality.geometry, clickedPoint);"
        ],
        "returnDescription": "True when `aGeom` covers `bGeom`."
      },
      {
        "key": "crosses",
        "description": "Tells whether the geometries cross: they share some but not all interior points and the\nintersection has a lower dimension than at least one of them (e.g. a line crossing a\npolygon boundary, two lines crossing at a point). Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.crosses",
        "examples": [
          "var roadCrossesRiver = ExtjsUtils.GEOMETRY.crosses(road.geometry, river.geometry);"
        ],
        "returnDescription": "True when the geometries cross."
      },
      {
        "key": "difference",
        "description": "Part of `aGeom` that is not covered by `bGeom` (A minus B). Invalid polygons may throw\na topology error — fix them with `validate` first. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry to subtract from.\n@param bGeom {OpenLayers.Geometry} Geometry to subtract.",
        "type": "function(aGeom, bGeom) : OpenLayers.Geometry|null",
        "qualifiedName": "ExtjsUtils.GEOMETRY.difference",
        "examples": [
          "var outsideReserve = ExtjsUtils.GEOMETRY.difference(property.geometry, reserve.geometry);"
        ],
        "returnDescription": "The difference, or null when nothing remains."
      },
      {
        "key": "disjoint",
        "description": "Tells whether the geometries have no point in common (the opposite of `intersects`).\nRequires the JSTS library.\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.disjoint",
        "examples": [
          "var farApart = ExtjsUtils.GEOMETRY.disjoint(a.geometry, b.geometry);"
        ],
        "returnDescription": "True when the geometries are disjoint."
      },
      {
        "key": "distance",
        "description": "Shortest distance between the two geometries (0 when they touch or intersect), in the\nunits of their projection — map units for map geometries. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : Number",
        "qualifiedName": "ExtjsUtils.GEOMETRY.distance",
        "examples": [
          "var d = ExtjsUtils.GEOMETRY.distance(clickedPoint, river.geometry);"
        ],
        "returnDescription": "The minimum distance in projection units."
      },
      {
        "key": "equalsExact",
        "description": "Tells whether the geometries are exactly equal: same type, same vertices in the same\norder (a strict, structural comparison). Use `equalsTopo` for \"same shape\" and\n`equalsNorm` to ignore vertex order. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.equalsExact",
        "examples": [
          "var unchanged = ExtjsUtils.GEOMETRY.equalsExact(original.geometry, edited.geometry);"
        ],
        "returnDescription": "True when the geometries are structurally identical."
      },
      {
        "key": "equalsNorm",
        "description": "Tells whether the geometries are exactly equal after normalizing both (canonical ring\norientation, start vertex and part order), i.e. the same vertices regardless of their\norder. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.equalsNorm",
        "examples": [
          "var sameRing = ExtjsUtils.GEOMETRY.equalsNorm(a.geometry, b.geometry);"
        ],
        "returnDescription": "True when the normalized geometries are identical."
      },
      {
        "key": "equalsTopo",
        "description": "Tells whether the geometries are topologically equal — they cover the same set of\npoints, even with different vertices (e.g. a square and the same square with an extra\ncollinear vertex). Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.equalsTopo",
        "examples": [
          "var sameShape = ExtjsUtils.GEOMETRY.equalsTopo(a.geometry, b.geometry);"
        ],
        "returnDescription": "True when the geometries represent the same shape."
      },
      {
        "key": "findIntersecting",
        "description": "Returns the candidates that intersect at least one of the edited geometries — e.g. to\nfind which existing polygons a freshly drawn one overlaps before merging them. The\ncheck is `safeIntersects` (topology errors are tolerated); null entries are ignored\nand the original OpenLayers objects are returned. Requires the JSTS library.\n@param editedGeoms {Array.<OpenLayers.Geometry>} Geometries to test against (e.g. the ones just drawn or modified).\n@param candidateGeoms {Array.<OpenLayers.Geometry>} Geometries to filter.",
        "type": "function(editedGeoms, candidateGeoms) : Array.<OpenLayers.Geometry>",
        "qualifiedName": "ExtjsUtils.GEOMETRY.findIntersecting",
        "examples": [
          "var touched = ExtjsUtils.GEOMETRY.findIntersecting([drawnFeature.geometry],\n    layer.features.map(function(f) { return f.geometry; }));"
        ],
        "returnDescription": "The entries of `candidateGeoms` intersecting any of `editedGeoms`."
      },
      {
        "key": "geomJstsToOl2",
        "description": "Converts a JSTS geometry (or array of geometries) back to its OpenLayers equivalent,\nvia a WKT round-trip. Empty JSTS geometries convert to null.\n@param jstsGeoms {Object|Array.<Object>} JSTS geometry or array of geometries to convert.",
        "type": "function(jstsGeoms) : OpenLayers.Geometry|Array.<OpenLayers.Geometry>",
        "qualifiedName": "ExtjsUtils.GEOMETRY.geomJstsToOl2",
        "returnDescription": "The converted geometry (or\n  array, matching the input shape); array entries are null for empty input geometries."
      },
      {
        "key": "geomOl2ToJsts",
        "description": "Converts an OpenLayers geometry (or array of geometries) to its JSTS equivalent, via a\nWKT round-trip. Requires the JSTS library to already be loaded (see\nGEOMETRY.loadAdvancedGeometryLibrary / the `?options=geom` URL option).\n@param olGeoms {OpenLayers.Geometry|Array.<OpenLayers.Geometry>} Geometry or array of\n  geometries to convert.",
        "type": "function(olGeoms) : Object|Array.<Object>|null",
        "qualifiedName": "ExtjsUtils.GEOMETRY.geomOl2ToJsts",
        "returnDescription": "The converted JSTS geometry (or array, matching\n  the input shape), or null if conversion failed."
      },
      {
        "key": "getArea",
        "description": "Area of a geometry (0 for points and lines), computed by JTS in the units of the\ngeometry's projection — squared map units (Web Mercator meters, distorted away from the\nequator) for geometries taken from the map. Requires the JSTS library (see\n`loadGeometryLibrary`).\n@param aGeom {OpenLayers.Geometry} Geometry to measure.",
        "type": "function(aGeom) : Number",
        "qualifiedName": "ExtjsUtils.GEOMETRY.getArea",
        "examples": [
          "var areaM2 = ExtjsUtils.GEOMETRY.getArea(feature.geometry);"
        ],
        "returnDescription": "The area in squared projection units."
      },
      {
        "key": "getCentroid",
        "description": "Centroid (center of mass) of a geometry — may fall outside a concave polygon; use\n`getInteriorPoint` for a point guaranteed to be inside. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : OpenLayers.Geometry",
        "qualifiedName": "ExtjsUtils.GEOMETRY.getCentroid",
        "examples": [
          "var center = ExtjsUtils.GEOMETRY.getCentroid(feature.geometry); // center.x, center.y"
        ],
        "returnDescription": "The centroid as an `OpenLayers.Geometry.Point`."
      },
      {
        "key": "getEnvelope",
        "description": "Bounding box of a geometry as a geometry: a rectangular `Polygon` (a `Point` or\n`LineString` for degenerate boxes). Use `getEnvelopeInternal` for the numeric bounds.\nRequires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : OpenLayers.Geometry",
        "qualifiedName": "ExtjsUtils.GEOMETRY.getEnvelope",
        "examples": [
          "var bboxPolygon = ExtjsUtils.GEOMETRY.getEnvelope(feature.geometry);"
        ],
        "returnDescription": "The envelope geometry."
      },
      {
        "key": "getEnvelopeInternal",
        "description": "Numeric bounding box of a geometry. Unlike the other operations this returns the raw\nJTS object, a `jsts.geom.Envelope` with `getMinX()`, `getMinY()`, `getMaxX()`,\n`getMaxY()`, `getWidth()`, `getHeight()`; for an OpenLayers bounds object use\n`geometry.getBounds()` instead. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : Object",
        "qualifiedName": "ExtjsUtils.GEOMETRY.getEnvelopeInternal",
        "examples": [
          "var env = ExtjsUtils.GEOMETRY.getEnvelopeInternal(feature.geometry); // env.getMinX()"
        ],
        "returnDescription": "A `jsts.geom.Envelope`."
      },
      {
        "key": "getInteriorPoint",
        "description": "A point guaranteed to lie inside the geometry (inside a polygon, on a line, one of the\npoints of a multipoint) — the right anchor for a label or a popup, unlike the centroid.\nRequires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : OpenLayers.Geometry",
        "qualifiedName": "ExtjsUtils.GEOMETRY.getInteriorPoint",
        "examples": [
          "var anchor = ExtjsUtils.GEOMETRY.getInteriorPoint(feature.geometry);"
        ],
        "returnDescription": "An `OpenLayers.Geometry.Point` inside the geometry."
      },
      {
        "key": "getLength",
        "description": "Length of a line, or perimeter of a polygon (0 for points), in the units of the\ngeometry's projection. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry to measure.",
        "type": "function(aGeom) : Number",
        "qualifiedName": "ExtjsUtils.GEOMETRY.getLength",
        "examples": [
          "var perimeter = ExtjsUtils.GEOMETRY.getLength(feature.geometry);"
        ],
        "returnDescription": "The length in projection units."
      },
      {
        "key": "getNumGeometries",
        "description": "Number of component geometries: the parts of a multi-geometry or collection, 1 for a\nsimple geometry. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : Number",
        "qualifiedName": "ExtjsUtils.GEOMETRY.getNumGeometries",
        "examples": [
          "var parts = ExtjsUtils.GEOMETRY.getNumGeometries(multiPolygon);"
        ],
        "returnDescription": "How many parts the geometry has."
      },
      {
        "key": "getSRID",
        "description": "Spatial reference id stored on the JTS geometry. Because every operation rebuilds the\nJTS geometry from WKT, this is always the JTS default (0): OpenLayers geometries carry\nno SRID and there is no way to set one through this API.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : Number",
        "qualifiedName": "ExtjsUtils.GEOMETRY.getSRID",
        "examples": [
          "ExtjsUtils.GEOMETRY.getSRID(feature.geometry); // 0"
        ],
        "returnDescription": "The SRID (0)."
      },
      {
        "key": "getUserData",
        "description": "User data stored on the JTS geometry. Because every operation rebuilds the JTS\ngeometry from WKT, this is always null: keep your data on the OpenLayers feature\n(`feature.attributes`) instead.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : *",
        "qualifiedName": "ExtjsUtils.GEOMETRY.getUserData",
        "examples": [
          "ExtjsUtils.GEOMETRY.getUserData(feature.geometry); // null"
        ],
        "returnDescription": "The user data (null)."
      },
      {
        "key": "intersection",
        "description": "Common part of the two geometries (A and B). Invalid polygons may throw a topology\nerror — fix them with `validate` first. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : OpenLayers.Geometry|null",
        "qualifiedName": "ExtjsUtils.GEOMETRY.intersection",
        "examples": [
          "var overlap = ExtjsUtils.GEOMETRY.intersection(property.geometry, reserve.geometry);"
        ],
        "returnDescription": "The intersection, or null when the geometries do not overlap."
      },
      {
        "key": "intersects",
        "description": "Tells whether the geometries share at least one point (touching counts). Throws a\ntopology error on invalid input — `safeIntersects` (on JSTS geometries) or\n`findIntersecting` tolerate that. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.intersects",
        "examples": [
          "var hit = ExtjsUtils.GEOMETRY.intersects(drawnPolygon, feature.geometry);"
        ],
        "returnDescription": "True when the geometries intersect."
      },
      {
        "key": "isGeometryCollection",
        "description": "Tells whether the geometry is a heterogeneous `GeometryCollection` (not a MultiPolygon/\nMultiLineString/MultiPoint). Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.isGeometryCollection",
        "examples": [
          "if (ExtjsUtils.GEOMETRY.isGeometryCollection(geom)) geom = geom.components[0];"
        ],
        "returnDescription": "True for a GeometryCollection."
      },
      {
        "key": "isJstsGeometry",
        "description": "Tells whether a value is a JSTS geometry (duck-typed on `getGeometryType`,\n`getBoundary` and `getEnvelopeInternal`), as opposed to an OpenLayers geometry. Useful\nwhen a function accepts both, e.g. before deciding whether to call `geomJstsToOl2`.\n@param obj {*} Value to check.",
        "type": "function(obj) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.isJstsGeometry",
        "examples": [
          "var ol = ExtjsUtils.GEOMETRY.isJstsGeometry(g) ? ExtjsUtils.GEOMETRY.geomJstsToOl2(g) : g;"
        ],
        "returnDescription": "True for a JSTS geometry object."
      },
      {
        "key": "isRectangle",
        "description": "Tells whether the geometry is an axis-aligned rectangle (a polygon with 5 vertices\nmatching its envelope). Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.isRectangle",
        "examples": [
          "var isBox = ExtjsUtils.GEOMETRY.isRectangle(drawnPolygon);"
        ],
        "returnDescription": "True for a rectangle."
      },
      {
        "key": "isSimple",
        "description": "Tells whether the geometry is simple in the OGC sense — no self-intersections or\nrepeated points (a line that crosses itself is not simple). Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.isSimple",
        "examples": [
          "if (!ExtjsUtils.GEOMETRY.isSimple(line)) ExtjsUtils.ALERTIFY.log(\"The line crosses itself.\");"
        ],
        "returnDescription": "True when the geometry is simple."
      },
      {
        "key": "isTopologyError",
        "description": "Tells whether an exception thrown by a geometry operation is a JTS `TopologyException`\n(invalid ring, self-intersection...), the kind of error that a zero-width buffer or\n`validate` usually fixes, as opposed to a programming error that should propagate.\n@param ex {Error|Object} The caught exception (anything falsy returns false).",
        "type": "function(ex) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.isTopologyError",
        "examples": [
          "try {\n    result = ExtjsUtils.GEOMETRY.intersection(a, b);\n} catch (ex) {\n    if (!ExtjsUtils.GEOMETRY.isTopologyError(ex)) throw ex;\n    result = ExtjsUtils.GEOMETRY.intersection(ExtjsUtils.GEOMETRY.validate(a, false), ExtjsUtils.GEOMETRY.validate(b, false));\n}"
        ],
        "returnDescription": "True when the exception is a topology error."
      },
      {
        "key": "isValid",
        "description": "Tells whether the geometry is valid in the OGC sense (closed rings, no\nself-intersecting rings, holes inside the shell...). Invalid polygons make the set\noperations throw topology errors — fix them with `validate`. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.isValid",
        "examples": [
          "var geom = ExtjsUtils.GEOMETRY.isValid(drawn) ? drawn : ExtjsUtils.GEOMETRY.validate(drawn, false);"
        ],
        "returnDescription": "True when the geometry is valid."
      },
      {
        "key": "loadAdvancedGeometryLibrary",
        "description": "Loads the JSTS geometry library (`/theme/app/js/jsts.min.js`) once and resolves when it\nis available; every other `GEOMETRY` operation throws until it is loaded (the\n`?options=geom` URL option loads it at startup). An optional callback runs right after\nthe load; when it returns a promise the returned promise waits for it. Safe to call\nseveral times.\n@param callback {function} Function to run once the library is loaded (may be async).",
        "type": "function(callback) : Promise.<Boolean>",
        "qualifiedName": "ExtjsUtils.GEOMETRY.loadAdvancedGeometryLibrary",
        "examples": [
          "await ExtjsUtils.GEOMETRY.loadAdvancedGeometryLibrary();\nvar merged = ExtjsUtils.GEOMETRY.unionAll(features.map(function(f) { return f.geometry; }));"
        ],
        "returnDescription": "Resolves to true when the library (and the callback) finished; rejects when the script cannot be loaded."
      },
      {
        "key": "loadGeometryLibrary",
        "description": "Alias of `GEOMETRY.loadAdvancedGeometryLibrary`: loads the JSTS library once and\nresolves when the geometry operations can be used.\n@param callback {function} Function to run once the library is loaded (may be async).",
        "type": "function(callback) : Promise.<Boolean>",
        "qualifiedName": "ExtjsUtils.GEOMETRY.loadGeometryLibrary",
        "examples": [
          "ExtjsUtils.GEOMETRY.loadGeometryLibrary(function() {\n    var hull = ExtjsUtils.GEOMETRY.convexHull(feature.geometry);\n});"
        ],
        "returnDescription": "Resolves to true when the library is loaded; rejects on a load failure."
      },
      {
        "key": "merge_intersects",
        "description": "Merges every set of mutually-intersecting geometries in the input array into a single\ngeometry each, leaving non-intersecting geometries untouched. Geometries are first\nclustered by bounding-box overlap (cheap) before the exact JSTS intersection/union\nwork runs only within each cluster.\n@param ol2Geometries {Array.<OpenLayers.Geometry>} Geometries to merge.\n@param mergeInvalid {Boolean} When false, two invalid geometries in the same\n  cluster are not merged with each other.",
        "type": "function(ol2Geometries, mergeInvalid) : Array.<OpenLayers.Geometry>",
        "qualifiedName": "ExtjsUtils.GEOMETRY.merge_intersects",
        "returnDescription": "The input geometries, with intersecting groups\n  replaced by their union."
      },
      {
        "key": "overlaps",
        "description": "Tells whether the geometries overlap: same dimension, they share some interior points\nbut neither contains the other (two partially overlapping polygons). Requires the JSTS\nlibrary.\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.overlaps",
        "examples": [
          "var partial = ExtjsUtils.GEOMETRY.overlaps(property.geometry, reserve.geometry);"
        ],
        "returnDescription": "True when the geometries partially overlap."
      },
      {
        "key": "safeIntersects",
        "description": "Checks whether two JSTS geometries intersect, retrying with a zero-width buffer if the\ndirect check throws a topology error. Returns false (instead of throwing) if the\nretry also fails.\n@param jstsA {Object} First JSTS geometry.\n@param jstsB {Object} Second JSTS geometry.",
        "type": "function(jstsA, jstsB) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.safeIntersects",
        "returnDescription": "True if the geometries intersect."
      },
      {
        "key": "safeSymDifference",
        "description": "Symmetric difference of two JSTS geometries (the area covered by exactly one of the\ntwo, i.e. union minus intersection), retrying with a zero-width buffer if the direct\noperation throws a topology error.\n@param jstsA {Object} First JSTS geometry.\n@param jstsB {Object} Second JSTS geometry.",
        "type": "function(jstsA, jstsB) : Object",
        "qualifiedName": "ExtjsUtils.GEOMETRY.safeSymDifference",
        "returnDescription": "The symmetric-difference JSTS geometry."
      },
      {
        "key": "safeUnion",
        "description": "Union of two JSTS geometries, retrying with a zero-width buffer (a common fix for\nself-intersecting/invalid rings) if the direct union throws a topology error.\n@param jstsA {Object} First JSTS geometry.\n@param jstsB {Object} Second JSTS geometry.",
        "type": "function(jstsA, jstsB) : Object",
        "qualifiedName": "ExtjsUtils.GEOMETRY.safeUnion",
        "returnDescription": "The unioned JSTS geometry."
      },
      {
        "key": "symDifference",
        "description": "Symmetric difference of the two geometries: the parts covered by exactly one of them\n(union minus intersection). Invalid polygons may throw a topology error — fix them with\n`validate` first. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : OpenLayers.Geometry|null",
        "qualifiedName": "ExtjsUtils.GEOMETRY.symDifference",
        "examples": [
          "var changed = ExtjsUtils.GEOMETRY.symDifference(before.geometry, after.geometry);"
        ],
        "returnDescription": "The symmetric difference, or null when the geometries are equal."
      },
      {
        "key": "toText",
        "description": "WKT (Well-Known Text) representation of the geometry, e.g. `POLYGON ((...))`, as written\nby JTS. Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry.",
        "type": "function(aGeom) : String",
        "qualifiedName": "ExtjsUtils.GEOMETRY.toText",
        "examples": [
          "var wkt = ExtjsUtils.GEOMETRY.toText(feature.geometry);"
        ],
        "returnDescription": "The WKT text."
      },
      {
        "key": "touches",
        "description": "Tells whether the geometries touch: they share boundary points only, with no interior\npoint in common (two neighbouring polygons sharing an edge). Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.touches",
        "examples": [
          "var neighbours = ExtjsUtils.GEOMETRY.touches(a.geometry, b.geometry);"
        ],
        "returnDescription": "True when the geometries touch without overlapping."
      },
      {
        "key": "union",
        "description": "Union of two geometries. Accepts and returns OpenLayers geometries (JSTS conversion is\nhandled internally).\n@param aGeom {OpenLayers.Geometry} First geometry.\n@param bGeom {OpenLayers.Geometry} Second geometry.",
        "type": "function(aGeom, bGeom) : OpenLayers.Geometry",
        "qualifiedName": "ExtjsUtils.GEOMETRY.union",
        "returnDescription": "The unioned geometry."
      },
      {
        "key": "unionAll",
        "description": "Unions many geometries at once (OpenLayers or JSTS objects, mixed), skipping null and\nempty entries. Uses the JTS cascaded union, retrying with a zero-width buffer and then\npairwise `safeUnion` when invalid geometries make it fail. Requires the JSTS library.\n@param geometries {Array} OL or JSTS geometry objects.\n@param options {Object} `{returnJsts: true}` returns the JSTS geometry instead of converting it back to OpenLayers.",
        "type": "function(geometries, options) : OpenLayers.Geometry|Object|null",
        "qualifiedName": "ExtjsUtils.GEOMETRY.unionAll",
        "examples": [
          "var merged = ExtjsUtils.GEOMETRY.unionAll(layer.features.map(function(f) { return f.geometry; }));\nif (merged) layer.addFeatures([new OpenLayers.Feature.Vector(merged)]);"
        ],
        "returnDescription": "The union (OpenLayers geometry, or JSTS when `returnJsts`), or null when there was nothing to union."
      },
      {
        "key": "validate",
        "description": "Fixes an invalid OpenLayers geometry (self-intersections, etc.) using JSTS, optionally\nsplitting a fixed multipolygon back into separate polygon geometries.\n@param olGeom {OpenLayers.Geometry} Geometry to validate/fix.\n@param splitMultipolygon {Boolean} Split a resulting MultiPolygon into\n  individual Polygon geometries instead of returning it as one MultiPolygon.",
        "type": "function(olGeom, splitMultipolygon) : OpenLayers.Geometry|Array.<OpenLayers.Geometry>|null",
        "qualifiedName": "ExtjsUtils.GEOMETRY.validate",
        "returnDescription": "The original geometry if\n  already valid, a fixed geometry otherwise, an array when split, or null if the\n  geometry couldn't be fixed."
      },
      {
        "key": "within",
        "description": "Tells whether `aGeom` lies within `bGeom` (the inverse of `contains`: every point of\n`aGeom` is inside `bGeom` and their interiors share a point). Requires the JSTS library.\n@param aGeom {OpenLayers.Geometry} Geometry to test.\n@param bGeom {OpenLayers.Geometry} Container geometry.",
        "type": "function(aGeom, bGeom) : Boolean",
        "qualifiedName": "ExtjsUtils.GEOMETRY.within",
        "examples": [
          "var insideState = ExtjsUtils.GEOMETRY.within(property.geometry, state.geometry);"
        ],
        "returnDescription": "True when `aGeom` is within `bGeom`."
      }
    ],
    "HideInterface": [
      {
        "key": "_information",
        "description": "Registers components that must stay visible when the interface is hidden.\nUsage: ExtjsUtils.HideInterface",
        "name": "HideInterface · keep visible when hidden"
      },
      {
        "key": "add",
        "description": "Registers an Ext component (or config/element) with the \"hide interface\" tool — the button\nthat hides the map's panels to leave only the image — so that the component is toggled\ntogether with the platform's own interface. An id is assigned to the component when it has\nnone. Returns the same object, so it can be used inline while building a config. Does\nnothing (returns undefined) when the HideInterface plugin is not loaded.\n@param comp {Ext.Component|Ext.Element|Object} Any Ext component, element or component config.",
        "type": "function(comp) : *",
        "qualifiedName": "ExtjsUtils.HideInterface.add",
        "examples": [
          "var panel = new Ext.Panel(ExtjsUtils.HideInterface.add({ html: \"Legend\", renderTo: Ext.getBody() }));"
        ],
        "returnDescription": "The same `comp`, now with an id, or undefined when the tool is unavailable."
      }
    ],
    "Extjs": [
      {
        "key": "_information",
        "completeAPI": "View the full <a target='_blank' href=\"https://docs.sencha.com/extjs/3.4.0\">Extjs API 3.4 click here</a>.\n*Only the functions which where used in CSR projects are listed <a href='/api/#category_Extjs'>here</a>.",
        "description": "Chart lookup (ExtjsUtils.HIGHCHART) and string helpers (ExtjsUtils.REGEX). The interface itself is built with Ext JS 3.4.",
        "name": "HIGHCHART, REGEX · charts and strings"
      },
      {
        "key": "escapeRegExp",
        "description": "Escapes the regular expression special characters of a string, so a user-typed value\ncan be used inside `new RegExp(...)` literally. The real call path is\n`ExtjsUtils.REGEX.escapeRegExp(str)`.\nEscaped characters are '-', '[', ']', '/', '{', '}', '(', ')', '*', '+', '?', '.', '\\', '^', '$', '|'.\n@param str {String} Expression to be escaped.",
        "type": "function(str) : String",
        "qualifiedName": "ExtjsUtils.REGEX.escapeRegExp",
        "examples": [
          "var re = new RegExp(ExtjsUtils.REGEX.escapeRegExp(\"CSR:layer.name (v2)\"), \"i\");"
        ],
        "returnDescription": "String with all regex characters escaped."
      },
      {
        "key": "getHighchartById",
        "description": "Gets a Highcharts chart instance by the id of the DOM element it was rendered into\n(`renderTo`), so query code can update a chart created in `descriptionHtml` after the\ndata arrives. The real call path is `ExtjsUtils.HIGHCHART.getById(id)`.\n@param id {String} Id of the DOM element the chart was rendered into.",
        "type": "function(id) : Object",
        "examples": [
          "var chart = ExtjsUtils.HIGHCHART.getById(\"highchart-01\");\nif (chart) chart.series[0].setData([10, 20, 30]);"
        ],
        "returnDescription": "The Highcharts chart object, or null when no chart uses that element."
      },
      {
        "key": "replaceAll",
        "description": "Replaces every occurrence of the plain substring `search` (no regular expression) by\n`replacement`. When `str` is an array of strings every entry is replaced in place and\nthe same array is returned. The real call path is `ExtjsUtils.REGEX.replaceAll(...)`.\n@param str {String|Array.<String>} A string, or an array of strings, whose terms are replaced.\n@param search {String} Substring to search for (literal, not a regex).\n@param replacement {String} Text that replaces each occurrence.",
        "type": "function(str, search, replacement) : String|Array.<String>",
        "qualifiedName": "ExtjsUtils.REGEX.replaceAll",
        "examples": [
          "ExtjsUtils.REGEX.replaceAll(\"a-b-c\", \"-\", \"_\"); // \"a_b_c\"\nExtjsUtils.REGEX.replaceAll([\"x.y\", \"z.w\"], \".\", \"/\"); // [\"x/y\", \"z/w\"]"
        ],
        "returnDescription": "The replaced string, or the same array when `str` is an array."
      }
    ],
    "HTMLUtils": [
      {
        "key": "_information",
        "description": "HTML, image and colour helpers: iframe detection, element positions, colour conversions and pixel reads.\nUsage: ExtjsUtils.HTML",
        "name": "HTML · elements, colours, pixels"
      },
      {
        "key": "cache",
        "description": "Cache of image contents keyed by URL: each entry is the base64 data URL of an image already\nloaded once (filled by the `onLoad` handler `getReusableImg` writes). Query code may also\nfill it directly with `getImageBase64`.",
        "type": "Object",
        "qualifiedName": "ExtjsUtils.HTML.cache",
        "examples": [
          "ExtjsUtils.HTML.cache['/theme/app/img/legend.png'] = ExtjsUtils.HTML.getImageBase64(imgElement);"
        ]
      },
      {
        "key": "canvasWebGL",
        "description": "The single off-screen `<canvas>` reused by every WebGL pixel read (`getWebGLContext`), so\na context is not created per call.",
        "type": "HTMLCanvasElement",
        "qualifiedName": "ExtjsUtils.HTML.canvasWebGL",
        "examples": [
          "var gl = ExtjsUtils.HTML.canvasWebGL.getContext('webgl');"
        ]
      },
      {
        "key": "changeImgColor",
        "description": "Replaces one colour of an image by another and writes the result back into the image\n(`img.src` becomes a data URL drawn on `canvas`). The `originalData` array keeps the\nuntouched pixels: it is filled on the first call (pass an empty array) and, on later calls\nwith the same array, the image is restored from it before the new colour is applied — so\nrepeated calls with different `toColor`s do not accumulate. Used to highlight a legend\nclass in a legend image.\n@param img {HTMLImageElement} Image to modify (loaded and CORS-readable).\n@param fromColor {Array.<Number>} `[r, g, b]` colour to replace (exact match).\n@param toColor {Array.<Number>} `[r, g, b]` replacement colour.\n@param canvas {HTMLCanvasElement} Canvas used to redraw the image.\n@param originalData {Array.<Number>} Array holding the original pixel bytes; pass `[]` on the first call and the same array afterwards.",
        "type": "function(img, fromColor, toColor, canvas, originalData)",
        "qualifiedName": "ExtjsUtils.HTML.changeImgColor",
        "examples": [
          "var original = [];\nvar canvas = document.createElement('canvas');\nExtjsUtils.HTML.changeImgColor(legendImg, [0, 128, 0], [255, 0, 0], canvas, original); // green -> red\nExtjsUtils.HTML.changeImgColor(legendImg, [0, 128, 0], [0, 0, 255], canvas, original); // green -> blue (red is gone)"
        ]
      },
      {
        "key": "colorStyleToRgb",
        "description": "Converts any colour the browser understands (`\"red\"`, `\"#f80\"`, `\"rgb(255, 136, 0)\"`,\n`\"hsl(...)\"`...) into its RGB components, by letting the browser compute the style of a\ntemporary element. The components come back as strings (e.g. `[\"255\", \"136\", \"0\"]`).\n@param color {String} The colour in any CSS notation.",
        "type": "function(color) : Array.<String>",
        "qualifiedName": "ExtjsUtils.HTML.colorStyleToRgb",
        "examples": [
          "ExtjsUtils.HTML.colorStyleToRgb('orange'); // [\"255\", \"165\", \"0\"]"
        ],
        "returnDescription": "The `[r, g, b]` components as strings (a fourth alpha component may be present for translucent colours)."
      },
      {
        "key": "getAbsolutePointFromLatLong",
        "description": "Returns the page position (pixels relative to the document) where a map coordinate is\ncurrently drawn: the map pixel of the coordinate plus the offset of the map viewport. The\ncoordinate must be in the map projection. Use it to place HTML (a popup, a marker image)\nover a map location.\n@param lonLat {OpenLayers.LonLat} The map coordinate, in the map projection.",
        "type": "function(lonLat) : Object",
        "qualifiedName": "ExtjsUtils.HTML.getAbsolutePointFromLatLong",
        "examples": [
          "var lonLat = new OpenLayers.LonLat(-44.0, -19.9).transform(\"EPSG:4326\", app.mapPanel.map.getProjection());\nvar point = ExtjsUtils.HTML.getAbsolutePointFromLatLong(lonLat);\npopup.style.left = point.x + 'px';\npopup.style.top = point.y + 'px';"
        ],
        "returnDescription": "`{x, y}` page position in pixels."
      },
      {
        "key": "getAbsolutePointFromMouseEvent",
        "description": "Returns the page position of a mouse event (`pageX`/`pageY`, or `clientX/Y` plus the body\nscroll on old IE), never negative. This is the `absolutePoint` expected by\n`LAYER.getLayerColorAtPoint` and `getRelativePositionToPoint`.\n@param mouseEvt {MouseEvent} The DOM mouse event.",
        "type": "function(mouseEvt) : Object",
        "qualifiedName": "ExtjsUtils.HTML.getAbsolutePointFromMouseEvent",
        "examples": [
          "ExtjsUtils.addDomListener(window, 'mousemove', function(evt) {\n  var point = ExtjsUtils.HTML.getAbsolutePointFromMouseEvent(evt);\n  popup.style.left = (point.x + 10) + 'px';\n  popup.style.top = (point.y + 10) + 'px';\n});"
        ],
        "returnDescription": "`{x, y}` page position in pixels."
      },
      {
        "key": "getImageBase64",
        "description": "Returns the content of a loaded image as a PNG data URL (`data:image/png;base64,...`),\ndrawing it on a reused 2D canvas. The image must be loaded and CORS-readable.\n@param img {HTMLImageElement} Loaded image element.",
        "type": "function(img) : String",
        "qualifiedName": "ExtjsUtils.HTML.getImageBase64",
        "examples": [
          "var dataUrl = ExtjsUtils.HTML.getImageBase64(document.getElementById('legend-img'));"
        ],
        "returnDescription": "The image as a base64 PNG data URL."
      },
      {
        "key": "getImageDataWithAlpha",
        "description": "Copies a rectangle of an image as raw RGBA bytes through WebGL, keeping the exact alpha\nvalues (a 2D canvas returns alpha-premultiplied colours). Defaults to the whole image.\n@param img {HTMLImageElement} Loaded, CORS-readable image.\n@param fromX {Number} Column of the first pixel to copy.\n@param fromY {Number} Row of the first pixel to copy.\n@param width {Number} Width of the rectangle, in pixels.\n@param height {Number} Height of the rectangle, in pixels.",
        "type": "function(img, fromX, fromY, width, height) : Uint8Array",
        "qualifiedName": "ExtjsUtils.HTML.getImageDataWithAlpha",
        "examples": [
          "var pixels = ExtjsUtils.HTML.getImageDataWithAlpha(tileImage); // whole tile\nvar transparentCount = 0;\nfor (var i = 3; i < pixels.length; i += 4) if (pixels[i] === 0) transparentCount++;"
        ],
        "returnDescription": "`4 * width * height` bytes: `R, G, B, A` per pixel, row by row."
      },
      {
        "key": "getIntermediateColor",
        "description": "Blends two colours with a weighted average: `weight` goes to the first colour and\n`1 - weight` to the second. Each colour may be an `[r, g, b]` array or any CSS colour\nstring (converted with `colorStyleToRgb`). Handy to build a gradient between two legend\ncolours.\n@param color1 {Array.<Number>|String} First colour.\n@param color2 {Array.<Number>|String} Second colour.\n@param weight {Number} Weight of the first colour, from 0 (only `color2`) to 1 (only `color1`).",
        "type": "function(color1, color2, weight) : Array.<Number>",
        "qualifiedName": "ExtjsUtils.HTML.getIntermediateColor",
        "examples": [
          "var mid = ExtjsUtils.HTML.getIntermediateColor('#ff0000', '#0000ff', 0.5); // [128, 0, 128]\nel.style.backgroundColor = ExtjsUtils.HTML.rgbToColorStyle(mid);"
        ],
        "returnDescription": "The blended colour as `[r, g, b]` (rounded 0-255 numbers)."
      },
      {
        "key": "getPixelColorAtWebGL",
        "description": "Reads the pixels of an image through WebGL (so the RGBA values are not alpha-premultiplied\nas a 2D canvas would make them): a window of `xWidth` x `yHeight` pixels whose top-left\ncorner is `(x, y)`, defaulting to a single pixel. The image must be loaded and\nCORS-readable (map tiles are).\n@param x {Number} Column of the (first) pixel, relative to the image.\n@param y {Number} Row of the (first) pixel, relative to the image.\n@param img {HTMLImageElement} Image to read from.\n@param xWidth {Number} Width of the window to read, in pixels.\n@param yHeight {Number} Height of the window to read, in pixels.",
        "type": "function(x, y, img, xWidth, yHeight) : Uint8Array",
        "qualifiedName": "ExtjsUtils.HTML.getPixelColorAtWebGL",
        "examples": [
          "var rgba = ExtjsUtils.HTML.getPixelColorAtWebGL(10, 20, tileImage); // Uint8Array [r, g, b, a]"
        ],
        "returnDescription": "`4 * xWidth * yHeight` bytes: `R, G, B, A` for each pixel, row by row."
      },
      {
        "key": "getPixelColorLimitsWebGL",
        "description": "Reads, through WebGL, only the pixels of an image that fall inside per-row x-spans — the\n`limits`/`bounds` produced by `LAYER.getLayerTilesFeatureIntersects` for a tile. `limits`\nis indexed by row and holds the `[start, end, start, end, ...]` columns to keep (closed\nintervals); e.g. `[[], [1, 3], [], [5, 5]]` keeps columns 1-3 of row 1 and column 5 of\nrow 3.\n@param img {HTMLImageElement} Image (tile) to read from.\n@param limits {Array.<Array.<Number>>} Per-row x-spans to keep, as described above.\n@param bbox {OpenLayers.Bounds} Row/column range to scan: `left`/`right` the columns, `top`/`bottom` the rows.",
        "type": "function(img, limits, bbox) : Uint8Array",
        "qualifiedName": "ExtjsUtils.HTML.getPixelColorLimitsWebGL",
        "examples": [
          "ExtjsUtils.LAYER.getLayerTilesFeatureIntersects(layer, feature).forEach(function(tile) {\n  var rgba = ExtjsUtils.HTML.getPixelColorLimitsWebGL(tile.img, tile.limits, tile.bounds);\n  console.log(rgba.length / 4, 'pixels inside the polygon on this tile');\n});"
        ],
        "returnDescription": "The `R, G, B, A` bytes of the kept pixels, concatenated."
      },
      {
        "key": "getRelativePositionToPoint",
        "description": "Converts a page position into a position relative to an element's top-left corner:\n`x` is positive to the right of the element's left edge (negative to the left), `y` is\npositive below its top edge (negative above). Defaults to the map viewport, which turns a\nmouse page position into a map pixel.\n@param absolutePoint {Object} Page position `{x, y}` in pixels (see `getAbsolutePointFromMouseEvent`).\n@param element {HTMLElement|Ext.Element} Reference element; defaults to the map viewport `div`.",
        "type": "function(absolutePoint, element) : Array.<Number>",
        "qualifiedName": "ExtjsUtils.HTML.getRelativePositionToPoint",
        "examples": [
          "var point = ExtjsUtils.HTML.getAbsolutePointFromMouseEvent(evt);\nvar mapPixel = ExtjsUtils.HTML.getRelativePositionToPoint(point); // [x, y] inside the map\nvar lonLat = app.mapPanel.map.getLonLatFromPixel({ x: mapPixel[0], y: mapPixel[1] });"
        ],
        "returnDescription": "`[x, y]` relative to the element."
      },
      {
        "key": "getReusableImg",
        "description": "Builds the HTML of an `<img>` tag meant to be loaded only once: the `src` is the base64\ncontent kept in `HTML.cache` when the URL was already loaded, otherwise the URL itself plus\nan `onLoad` handler that stores the image in the cache for the next call. Extra attributes\nare appended verbatim. Note: the current implementation reads an undefined\n`HTML.urlCache` before appending the `onLoad` handler, so calling it throws a `TypeError`\ntoday; no production query uses it.\n@param url {String} URL of the image.\n@param attributes {String} Extra attributes to append to the tag, e.g. `' width=\"16\" class=\"icon\"'`.",
        "type": "function(url, attributes) : String",
        "qualifiedName": "ExtjsUtils.HTML.getReusableImg",
        "examples": [
          "var html = ExtjsUtils.HTML.getReusableImg('/theme/app/img/legend.png', ' class=\"legend-icon\"');"
        ],
        "returnDescription": "The `<img ...>` HTML string."
      },
      {
        "key": "getWebGLContext",
        "description": "Returns the WebGL rendering context of the shared `canvasWebGL` canvas (trying the\n`\"webgl\"` and then the legacy `\"experimental-webgl\"` context names). `null` when the\nbrowser has no WebGL support — see `CHECK.browserSupported`.",
        "type": "function() : WebGLRenderingContext",
        "qualifiedName": "ExtjsUtils.HTML.getWebGLContext",
        "examples": [
          "var gl = ExtjsUtils.HTML.getWebGLContext();\nif (!gl) ExtjsUtils.ALERTIFY.alert('This browser cannot read map pixels');"
        ],
        "returnDescription": "The WebGL context, or `null` when unavailable."
      },
      {
        "key": "hexToRgb",
        "description": "Converts a 6-digit hexadecimal colour (`\"#rrggbb\"`, the `#` being optional) into an\n`[r, g, b]` array of 0-255 numbers. Short (`#rgb`) or invalid strings give `null`.\n@param hex {String} The colour in hexadecimal notation.",
        "type": "function(hex) : Array.<Number>|null",
        "qualifiedName": "ExtjsUtils.HTML.hexToRgb",
        "examples": [
          "ExtjsUtils.HTML.hexToRgb('#ff8800'); // [255, 136, 0]"
        ],
        "returnDescription": "`[r, g, b]`, or `null` when the string is not a 6-digit hex colour."
      },
      {
        "key": "isInsideIframe",
        "description": "Tells whether the map page is embedded in another page (an `iframe`), which is how most\ntenant sites show a query. Cross-origin embedding that blocks the check also counts as\nembedded. Use it to hide interface parts that only make sense when the map is opened on its\nown.",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.HTML.isInsideIframe",
        "examples": [
          "if (ExtjsUtils.HTML.isInsideIframe()) ExtjsUtils.CSS.defineClass('#topbar', 'display: none;');"
        ],
        "returnDescription": "`true` when running inside an iframe, `false` when opened directly."
      },
      {
        "key": "offset",
        "description": "Returns the position of an element relative to the whole page (document), taking the\ncurrent scroll into account — unlike `getBoundingClientRect`, which is relative to the\nviewport. Accepts a DOM element or an `Ext.Element`.\n@param element {HTMLElement|Ext.Element} Element to measure.",
        "type": "function(element) : Object",
        "qualifiedName": "ExtjsUtils.HTML.offset",
        "examples": [
          "var pos = ExtjsUtils.HTML.offset(app.mapPanel.map.viewPortDiv);\nconsole.log('map starts at', pos.left, pos.top);"
        ],
        "returnDescription": "`{top, left, right}` — page offsets in pixels of the element's top, left and right edges."
      },
      {
        "key": "rgbToColorStyle",
        "description": "Formats an `[r, g, b]` colour as the CSS string `\"rgb(r, g, b)\"`. The usual companion of\nthe legend entries (`layer.getLegendEntries()[i].color` is an `[r, g, b]` array) when\npainting HTML with a legend colour.\n@param color {Array.<Number>} The colour components `[r, g, b]` (a fourth element is ignored).",
        "type": "function(color) : String",
        "qualifiedName": "ExtjsUtils.HTML.rgbToColorStyle",
        "examples": [
          "var entry = layer.getLegendEntries()[0];\nhtml += '<span style=\"color:' + ExtjsUtils.HTML.rgbToColorStyle(entry.color) + '\">' + entry.title + '</span>';"
        ],
        "returnDescription": "The `\"rgb(r, g, b)\"` CSS colour."
      }
    ],
    "INTROJS": [
      {
        "key": "_information",
        "description": "Guided tours (intro.js) for the interface.\nUsage: ExtjsUtils.INTROJS",
        "name": "INTROJS · guided tours"
      },
      {
        "key": "getStep",
        "description": "Builds one Intro.js step object for `loadAndRun`. Only the fields that were given are\nset (`position` defaults to `'auto'`).\n@param element {HTMLElement} Element to highlight; omit for a step without a target (centered message).\n@param txt {String} Text/HTML shown in the step tooltip.\n@param position {String} Tooltip position: `top`, `left`, `right`, `bottom`, `bottom-left-aligned`, `bottom-middle-aligned`, `bottom-right-aligned` or `auto`.\n@param step {Number} Explicit step number (order); `0` is accepted.\n@param tooltipClass {String} Extra CSS class for the tooltip.\n@param highlightClass {String} Extra CSS class for the highlighted element.\n@param disableInteraction {Boolean} True to block clicks on the highlighted element during the step.",
        "type": "function(element, txt, position, step, tooltipClass, highlightClass, disableInteraction) : Object",
        "qualifiedName": "ExtjsUtils.INTROJS.getStep",
        "examples": [
          "var step = ExtjsUtils.INTROJS.getStep(document.getElementById(\"chart\"), \"Click a bar to filter the map.\", \"top\");"
        ],
        "returnDescription": "The Intro.js step object."
      },
      {
        "key": "getStepsFromDOM",
        "description": "Collects tour steps from the DOM: every visible element (larger than 13x13 px) with a\n`data-intro` attribute becomes a step, reading `data-position`, `data-step`,\n`data-tooltipClass` and `data-highlightClass` as well. This is what `loadAndRun` does\nwhen no steps are given.\n@param parent {HTMLElement|Document} Root element to search under.",
        "type": "function(parent) : Array.<Object>",
        "qualifiedName": "ExtjsUtils.INTROJS.getStepsFromDOM",
        "examples": [
          "var steps = ExtjsUtils.INTROJS.getStepsFromDOM(document.getElementById(\"mypanel\"));"
        ],
        "returnDescription": "The step objects, in DOM order."
      },
      {
        "key": "loadAndRun",
        "description": "Loads Intro.js (script and stylesheets, once) and starts a guided tour. Without `steps`\nthe tour is built from every visible element carrying a `data-intro` attribute\n(`getStepsFromDOM`), wrapped in a welcome and a completion step. Steps whose element is\nnot visible are skipped automatically and the tooltip is kept inside the viewport.\n@param steps {Array.<Object>} Intro.js step objects (see `getStep`); omit to build them from the DOM.\n@param exitOnOverlayClick {Boolean} Whether clicking the dark overlay closes the tour.\n@param startFunction {function} Receives the configured introJs instance instead of starting it right away (call `intro.start()` yourself).",
        "type": "function(steps, exitOnOverlayClick, startFunction)",
        "qualifiedName": "ExtjsUtils.INTROJS.loadAndRun",
        "examples": [
          "ExtjsUtils.INTROJS.loadAndRun([\n    ExtjsUtils.INTROJS.getStep(null, \"Welcome to the fire history map.\"),\n    ExtjsUtils.INTROJS.getStep(document.getElementById(\"legend\"), \"Colours show the number of fires.\", \"left\")\n]);"
        ]
      },
      {
        "key": "setHelpDescriptionDOM",
        "description": "Marks a DOM element as a tour step by setting its `data-intro`, `data-position` and\n(optionally) `data-step` attributes, so `getStepsFromDOM`/`loadAndRun` pick it up.\n@param dom {HTMLElement} Element to annotate.\n@param txt {String} Help text; nothing is set when empty.\n@param position {String} Tooltip position (see `getStep`); invalid values fall back to `auto`.\n@param priority {Number} Step order, stored as `data-step`.",
        "type": "function(dom, txt, position, priority) : HTMLElement",
        "qualifiedName": "ExtjsUtils.INTROJS.setHelpDescriptionDOM",
        "examples": [
          "ExtjsUtils.INTROJS.setHelpDescriptionDOM(document.getElementById(\"legend\"), \"Colours show the number of fires.\", \"left\", 1);"
        ],
        "returnDescription": "The same element."
      },
      {
        "key": "setHelpDescriptionElement",
        "description": "Attaches a help description to an Ext component **config** (not a rendered component):\nan `afterrender` listener is chained so that `setHelpDescriptionDOM` runs on the\ncomponent element once it exists. Returns the same config, so it can be used inline.\nThrows when given an already rendered component.\n@param cmpConfig {Object} Ext component configuration object.\n@param txt {String} Help text; nothing is attached when empty.\n@param position {String} Tooltip position (see `getStep`); invalid values fall back to `auto`.\n@param priority {Number} Step order, stored as `data-step`.",
        "type": "function(cmpConfig, txt, position, priority) : Object",
        "qualifiedName": "ExtjsUtils.INTROJS.setHelpDescriptionElement",
        "examples": [
          "var btn = ExtjsUtils.INTROJS.setHelpDescriptionElement({xtype: \"button\", text: \"Export\"},\n    \"Downloads the current table as CSV.\", \"bottom\", 3);"
        ],
        "returnDescription": "The same `cmpConfig`."
      }
    ],
    "JS": [
      {
        "key": "_information",
        "description": "Access to the running application objects: the OpenLayers map, the GeoExplorer app and the layer sources.\nUsage: ExtjsUtils.JS",
        "name": "JS · the map and the app"
      },
      {
        "key": "getApp",
        "description": "Returns the application object (`window.app`, the GeoExplorer viewer): `app.mapPanel` holds\nthe map and its layer store, `app.layerSources` the registered sources, `app.tools` the\ntools by id.",
        "type": "function() : GeoExplorer",
        "qualifiedName": "ExtjsUtils.JS.getApp",
        "examples": [
          "var layerStore = ExtjsUtils.JS.getApp().mapPanel.layers;"
        ],
        "returnDescription": "The application instance."
      },
      {
        "key": "getLayerSource",
        "description": "Returns a registered layer source by id — `\"local\"` (the Mappia GeoServer), the built-in\n`\"osm\"`/`\"google\"`, or an id registered with `QUERY.addRemoteWMSServer` — or null when it\ndoes not exist (or is still not created). Sources expose `createLayerRecord(cfg)` and, for\nWMS sources, the capabilities `store`.\n@param sourceId {String} Source id as used in a layer's `source` property.",
        "type": "function(sourceId) : gxp.plugins.LayerSource|null",
        "qualifiedName": "ExtjsUtils.JS.getLayerSource",
        "examples": [
          "var src = ExtjsUtils.JS.getLayerSource(\"ibge\");\nif (src) console.log(\"IBGE source is registered\");"
        ],
        "returnDescription": "The source plugin, or null when unknown."
      },
      {
        "key": "getMap",
        "description": "Returns the OpenLayers map of the page (`window.map` when defined, otherwise\n`app.mapPanel.map`). Prefer it over the bare globals in query code: it works in the viewer,\nthe editor and embedded pages alike.",
        "type": "function() : OpenLayers.Map",
        "qualifiedName": "ExtjsUtils.JS.getMap",
        "examples": [
          "ExtjsUtils.JS.getMap().zoomTo(6);"
        ],
        "returnDescription": "The current map."
      }
    ],
    "JSONUtils": [
      {
        "key": "_information",
        "description": "JSON helpers that keep functions when serializing (used to store layer definitions).\nUsage: ExtjsUtils.JSON",
        "name": "JSON · JSON that keeps functions"
      },
      {
        "key": "stringify",
        "description": "Serializes an object to JavaScript source text, keeping what `JSON.stringify` drops:\nfunctions are written with their source (`toString()`), and numbers, booleans, arrays\nand nested objects are kept. `undefined`/`null` values are omitted. The output is\nJavaScript (unquoted function bodies), meant to be evaluated again — it is how layer\ndefinitions with callbacks are stored — not strict JSON. Markup braces `{{`/`}}`\ninside strings are escaped.\n@param obj {Object} The object to serialize.\n@param maxDeep {Number} Maximum recursion depth, to stop on cyclic references.\n@param indent {Number} Number of spaces per indentation level (0 or omitted for none).\n@param clearDataCallback {function} `function(curObj, curKey)` returning true to skip the property `curKey` of `curObj`.",
        "type": "function(obj, maxDeep, indent, clearDataCallback) : String|undefined",
        "qualifiedName": "ExtjsUtils.JSON.stringify",
        "examples": [
          "var src = ExtjsUtils.JSON.stringify({name: \"CSR:example_layer\", functions: {onClick: function(p) { alert(p); }}}, 7, 2,\n    function(obj, key) { return key === \"cache\"; });"
        ],
        "returnDescription": "The source text, or undefined for a null/undefined input."
      }
    ],
    "Layer": [
      {
        "key": "_information",
        "description": "Find layers by name, read their extents, legends and pixel colours. Usage: ExtjsUtils.LAYER",
        "name": "LAYER · find layers, extents, legends, pixels"
      },
      {
        "key": "filterRecordsProperties",
        "description": "Extracts properties from an array of layer records (or plain objects). With a single\nproperty name the result is a flat array of values; with several names each element is an\nobject holding those properties. A single record or a single property name (not wrapped in\nan array) is accepted too.\n@param records {Array.<Ext.data.Record>|Ext.data.Record|Array.<Object>} Records (or objects) to read; a record's `get(name)` is used when available.\n@param properties {Array.<String>|String} Property name(s) to extract.",
        "type": "function(records, properties) : Array.<Object>|Array.<(String|Number)>",
        "qualifiedName": "ExtjsUtils.LAYER.filterRecordsProperties",
        "examples": [
          "var records = ExtjsUtils.LAYER.getLayersRecords();\nvar names = ExtjsUtils.LAYER.filterRecordsProperties(records, 'name');            // [\"CSR:estados\", ...]\nvar pairs = ExtjsUtils.LAYER.filterRecordsProperties(records, ['name', 'title']); // [{name, title}, ...]"
        ],
        "returnDescription": "One entry per record: the value itself for a single property, an object for several."
      },
      {
        "key": "getActiveLayersDefinition",
        "description": "Returns a copy of the layer records currently in the map (the map panel's layer store),\nin map order. Each record's `data`/`json` carries the layer definition as the query declared\nit, after the interpreter filled in its defaults, and `record.getLayer()` gives the\nOpenLayers layer.",
        "type": "function() : Array.<GeoExt.data.LayerRecord>",
        "qualifiedName": "ExtjsUtils.LAYER.getActiveLayersDefinition",
        "examples": [
          "ExtjsUtils.LAYER.getActiveLayersDefinition().forEach(function(record) {\n  console.log(record.get('name'), record.get('group'));\n});"
        ],
        "returnDescription": "A new array with all the layer records."
      },
      {
        "key": "getAllBackgroundLayerRecords",
        "description": "Returns the layer records of every background map currently in the map's layer store —\nthe layers declared with `group: \"background\"` (or flagged `isBaseLayer`), including the\nplatform default basemap. Use it to toggle basemaps from query code.",
        "type": "function() : Array.<GeoExt.data.LayerRecord>",
        "qualifiedName": "ExtjsUtils.LAYER.getAllBackgroundLayerRecords",
        "examples": [
          "// Hide every basemap\nExtjsUtils.LAYER.getAllBackgroundLayerRecords().forEach(function(record) {\n  record.getLayer().setVisibility(false);\n});"
        ],
        "returnDescription": "The background layer records (empty when there is none)."
      },
      {
        "key": "getLayerByName",
        "description": "Returns the OpenLayers layer currently in the map whose `name` equals the given name, or\n`undefined` when no such layer is loaded. A calculated layer (`source: \"calculate\"`) is\nnamed on the map by the `name` declared in the query (`'CSR:altimetria'`); a plain map\nlayer is named by its `title`. To find a plain layer by the map it shows, filter\n`ExtjsUtils.JS.getMap().layers` on `layer.params.LAYERS === 'CSR:estados'`.\n@param fullLayerName {String} The layer's name on the map: the query `name` of a calculated layer, the `title` of a plain one.",
        "type": "function(fullLayerName) : OpenLayers.Layer|undefined",
        "qualifiedName": "ExtjsUtils.LAYER.getLayerByName",
        "examples": [
          "var layer = ExtjsUtils.LAYER.getLayerByName('CSR:altimetria'); // a calculated layer\nif (layer) layer.setVisibility(true);"
        ],
        "returnDescription": "The layer object, or `undefined` when it is not in the map."
      },
      {
        "key": "getLayerColorAtPoint",
        "description": "Reads the colour a layer draws under a page position: looks through the layer's tile images\nfor the one containing the point and reads that pixel through WebGL. Returns `null` when\nthe point falls outside every tile or the pixel is a \"null\" colour for the layer operation\n(transparent / no data). This is what `isOnHover` uses.\n@param layer {OpenLayers.Layer} Layer whose tiles are inspected (an inner layer, not the composed one).\n@param absolutePoint {Object} Page position `{x, y}` in pixels (e.g. from `HTML.getAbsolutePointFromMouseEvent`).\n@param layerOperation {Object} The layer's `MapOperations` value, used to decide which colours mean \"no data\".",
        "type": "function(layer, absolutePoint, layerOperation) : Uint8Array|null",
        "qualifiedName": "ExtjsUtils.LAYER.getLayerColorAtPoint",
        "examples": [
          "var point = ExtjsUtils.HTML.getAbsolutePointFromMouseEvent(evt);\nvar rgba = ExtjsUtils.LAYER.getLayerColorAtPoint(composedLayer.layers[0], point);\nif (rgba) console.log('hovering colour', ExtjsUtils.HTML.rgbToColorStyle(rgba));"
        ],
        "returnDescription": "The `[R, G, B, A]` pixel colour, or `null` when there is no colour at that point."
      },
      {
        "key": "getLayerExtent",
        "description": "Returns the extent of a layer in the map projection. A vector layer with features gives their\nbounds; any other layer its `llbbox` (the lon/lat bounding box read from the WMS\ncapabilities) reprojected. When the layer has no `llbbox`, or the reprojected box is invalid,\nit falls back to the layer's `maxExtent` (or the inner layer's `maxExtent` for a composed\nlayer) and finally to the map's `maxExtent`. The layer may be given by name (`layer.name` or\n`getMapName`). Typical use: zoom the map to a layer.\n@param layer {OpenLayers.Layer|String} The layer (a composed layer or one of its inner layers), or its name.",
        "type": "function(layer) : OpenLayers.Bounds",
        "qualifiedName": "ExtjsUtils.LAYER.getLayerExtent",
        "examples": [
          "// Zoom to a layer when it becomes visible\nonVisibilityChange: function(visible) {\n  if (visible) app.mapPanel.map.zoomToExtent(ExtjsUtils.LAYER.getLayerExtent(this), true);\n}",
          "// the bounding box of a property drawn by a vector layer\nvar bounds = ExtjsUtils.LAYER.getLayerExtent(\"CSR:show_car\");"
        ],
        "returnDescription": "The layer extent in the map projection; `null` for a name not on the map."
      },
      {
        "key": "getLayerLegend",
        "description": "Gets the associated layer legend content by its index.\n\nPS: The legend format is the same as expected by the composedLayer.setCalculateLegend function.\nUsage: this.setCalculateLegend(ExtjsUtils.LAYER.getLayerLegend(this, 0)); inside a calculated layer's beforeCalc.\n@param composedLayer {OpenLayers.Layer.Composed} Layer composed.\n@param index {Numeric} Index of the inside layer.",
        "type": "function(composedLayer, index) : Array",
        "qualifiedName": "ExtjsUtils.LAYER.getLayerLegend",
        "returnDescription": "Array with the legend entries [{color, value, title}, ...]."
      },
      {
        "key": "getLayerSource",
        "description": "Finds the source (`local`, `remote`, a source registered by `addRemoteWMSServer`...) that\nserves a layer: first the source whose store contains a record with that `name`, then the\nsource whose id matches the name's workspace prefix (`workspace:layer`). Returns either the\nlive source plugin or its description object.\n@param layerName {String} Full layer name, e.g. `'CSR:estados'`.\n@param onlyDescription {Boolean} `true` to get a copy of the source description (the `sources` config: `url`, `ptype`, `cors`...), `false`/omitted to get the source plugin instance.",
        "type": "function(layerName, onlyDescription) : Object",
        "qualifiedName": "ExtjsUtils.LAYER.getLayerSource",
        "examples": [
          "var wmsUrl = ExtjsUtils.LAYER.getLayerSource('CSR:estados', true).url;"
        ],
        "returnDescription": "The source plugin (e.g. a `gxp.plugins.WMSSource`) or its description, or an empty object `{}` when no source contains the layer."
      },
      {
        "key": "getLayerSourceNames",
        "description": "Returns the ids of every layer source currently registered in the application — the\nbuilt-in ones (`local`, `osm`, `google`...) plus those added by the query\n(`QUERY.addRemoteWMSServer`). These ids are the `storeName` accepted by `getLayersRecords`\nand `getStoreLayersNames`.",
        "type": "function() : Array.<String>",
        "qualifiedName": "ExtjsUtils.LAYER.getLayerSourceNames",
        "examples": [
          "ExtjsUtils.LAYER.getLayerSourceNames(); // [\"local\", \"osm\", \"google\", \"remote\", ...]"
        ],
        "returnDescription": "The source ids (empty before the application is ready)."
      },
      {
        "key": "getLayerTilesFeatureIntersects",
        "description": "Rasterises a polygon feature over the tiles of a layer and returns the tiles it intersects,\neach with the exact pixel spans inside the polygon (scan-line polygon fill, one entry per\ntile row). This is the basis of the \"values inside a drawn area\" calculations; pass the\nresult rows to `HTML.getPixelColorLimitsWebGL` to read the pixels.\n@param layer {OpenLayers.Layer} Layer whose tile images are tested.\n@param feature {OpenLayers.Feature.Vector} Polygon feature, in the layer projection.",
        "type": "function(layer, feature) : Array.<Object>",
        "qualifiedName": "ExtjsUtils.LAYER.getLayerTilesFeatureIntersects",
        "examples": [
          "var tiles = ExtjsUtils.LAYER.getLayerTilesFeatureIntersects(composedLayer.layers[0], drawnFeature);\ntiles.forEach(function(tile) {\n  var pixels = ExtjsUtils.HTML.getPixelColorLimitsWebGL(tile.img, tile.limits, tile.bounds);\n});"
        ],
        "returnDescription": "One `{img, limits, bounds}` per intersecting tile: `img` the tile image, `limits` an array indexed by tile row holding the `[start, end, start, end, ...]` x-spans inside the polygon, `bounds` an `OpenLayers.Bounds` with the row/column range covered (closed interval)."
      },
      {
        "key": "getLayerTilesInArea",
        "description": "Returns the tile images of a layer that overlap a rectangle given in page pixels, together\nwith the part of each tile that lies inside the rectangle. Useful to read the pixels of a\nscreen region (e.g. a dragged box) from the layer tiles.\n@param layer {OpenLayers.Layer} Layer whose tile images are tested.\n@param minBBOXx {Number} Left edge of the rectangle, in page pixels.\n@param minBBOXy {Number} Top edge of the rectangle, in page pixels.\n@param maxBBOXx {Number} Right edge of the rectangle, in page pixels.\n@param maxBBOXy {Number} Bottom edge of the rectangle, in page pixels.",
        "type": "function(layer, minBBOXx, minBBOXy, maxBBOXx, maxBBOXy) : Array.<Object>",
        "qualifiedName": "ExtjsUtils.LAYER.getLayerTilesInArea",
        "examples": [
          "var tiles = ExtjsUtils.LAYER.getLayerTilesInArea(composedLayer.layers[0], 100, 100, 400, 300);\ntiles.forEach(function(tile) {\n  var w = tile.maxXY[0] - tile.minXY[0], h = tile.maxXY[1] - tile.minXY[1];\n  var pixels = ExtjsUtils.HTML.getPixelColorAtWebGL(tile.minXY[0], tile.minXY[1], tile.img, w, h);\n});"
        ],
        "returnDescription": "One `{img, minXY, maxXY}` per overlapping tile: `img` the tile image, `minXY`/`maxXY` the `[x, y]` corners of the overlap relative to the tile."
      },
      {
        "key": "getLayersRecords",
        "description": "Gets the store record of a layer from its full name (`'CSR:estados'`), searching every\nsource store or only the one given by `storeName`. The record holds the layer's\ncapabilities data (`title`, `abstract`, `styles`, `llbbox`...). Called without\n`layerName` it returns the records of all the layers the source(s) offer.\n@param layerName {String} Full layer name to look for; omit to get every record.\n@param storeName {String} Source id to restrict the search to (see `getLayerSourceNames`); omit to search all sources.",
        "type": "function(layerName, storeName) : Array.<Ext.data.Record>|Ext.data.Record|null",
        "qualifiedName": "ExtjsUtils.LAYER.getLayersRecords",
        "examples": [
          "var record = ExtjsUtils.LAYER.getLayersRecords(\"CSR:estados\");\nif (record) console.log(record.get(\"title\"), record.get(\"styles\"));\n\n// Every layer offered by the 'remote' source\nvar remoteRecords = ExtjsUtils.LAYER.getLayersRecords(undefined, \"remote\");"
        ],
        "returnDescription": "The record of `layerName` (`null` when not found in exactly one store), or the array of all records when `layerName` is omitted."
      },
      {
        "key": "getLocalLayers",
        "description": "Filters an array of layers down to the visible WMS layers. Composed (calculation) layers\nthat have no expression are replaced by their inner layers, which go through the same\ntest. The input array is not modified.\n@param layersArray {Array.<OpenLayers.Layer>} Layers to filter, typically `app.mapPanel.map.layers`.",
        "type": "function(layersArray) : Array.<OpenLayers.Layer>",
        "qualifiedName": "ExtjsUtils.LAYER.getLocalLayers",
        "examples": [
          "var visibleWms = ExtjsUtils.LAYER.getLocalLayers(app.mapPanel.map.layers);"
        ],
        "returnDescription": "The visible WMS layers found."
      },
      {
        "key": "getMapName",
        "description": "The stable name of a map layer: the WMS `LAYERS` it requests (e.g. `\"CSR:cultivo_cafe_cafe\"`),\nor `layer.name` for layers without one (vector, XYZ/OSM — `\"OSM Mapnik\"`). A WMS layer's own\n`name` is replaced by its display title once its capabilities load, so it cannot identify the\nmap; this can. Offline areas record their maps (`layerNames`) under this name.\n@param layer {OpenLayers.Layer} The layer.",
        "type": "function(layer) : String",
        "qualifiedName": "ExtjsUtils.LAYER.getMapName",
        "examples": [
          "var names = app.mapPanel.map.layers.map(ExtjsUtils.LAYER.getMapName);"
        ],
        "returnDescription": "The map name."
      },
      {
        "key": "getPixelCoordinateSize",
        "description": "Returns the size of one screen pixel in map units at the current zoom level, as the\ndifference between the coordinates of pixels `(0, 0)` and `(1, 1)` expressed in the given\nprojection. Only meaningful for projections where the pixel size is constant across the\nview.\n@param projection {String|OpenLayers.Projection} Projection to express the size in (e.g. `layer.projection`); omit to use the map projection.",
        "type": "function(projection) : Object",
        "qualifiedName": "ExtjsUtils.LAYER.getPixelCoordinateSize",
        "examples": [
          "var px = ExtjsUtils.LAYER.getPixelCoordinateSize(\"EPSG:4326\");\nconsole.log(\"one pixel spans\", px.lon, \"degrees of longitude\");"
        ],
        "returnDescription": "`{lon, lat}` — width and height of a pixel in units of `projection`."
      },
      {
        "key": "getSourceByName",
        "description": "Returns the source plugin instance registered under an id (e.g. `'local'`), whose `store`\nholds one record per layer the source offers. `undefined` for an unknown id.\n@param sourceName {String} Source id, one of `getLayerSourceNames()`.",
        "type": "function(sourceName) : Object",
        "qualifiedName": "ExtjsUtils.LAYER.getSourceByName",
        "examples": [
          "var localStore = ExtjsUtils.LAYER.getSourceByName('local').store;"
        ],
        "returnDescription": "The source plugin (e.g. a `gxp.plugins.WMSSource`)."
      },
      {
        "key": "getStorageBaseUrl",
        "description": "Builds the base URL of the pre-computed (\"storage\") responses of a WMS layer and style:\nthe layer source URL (without its `getCapabilities.xml` suffix) followed by\n`/<layer name without workspace>/<style name or 1>/`. Internal to the storage-backed\ncalculations; listed for completeness.\n@param storageLayer {OpenLayers.Layer.WMS} WMS layer whose `params.LAYERS` and `params.STYLES` identify the stored responses.",
        "type": "function(storageLayer) : String",
        "qualifiedName": "ExtjsUtils.LAYER.getStorageBaseUrl",
        "examples": [
          "var base = ExtjsUtils.LAYER.getStorageBaseUrl(layer); // \".../estados/1/\""
        ],
        "returnDescription": "The base URL, ending with `/`."
      },
      {
        "key": "getStoreLayerStylesFromName",
        "description": "Returns the names of the styles a layer is published with, read from the layer's store\nrecord (the WMS capabilities). Empty when the layer is unknown.\n@param layerName {String} Full layer name, e.g. `'CSR:estados'`.",
        "type": "function(layerName) : Array.<String>",
        "qualifiedName": "ExtjsUtils.LAYER.getStoreLayerStylesFromName",
        "examples": [
          "// Add every style of a remote layer as its own map entry\nExtjsUtils.LAYER.getStoreLayerStylesFromName('remote:landuse').forEach(function(style) {\n  ExtjsUtils.QUERY.addLayer({ name: 'remote:landuse', title: style, styles: style, source: 'remote' });\n});"
        ],
        "returnDescription": "The style names of the layer."
      },
      {
        "key": "getStoreLayersNames",
        "description": "Returns the full names of all the layers offered by the sources — every source, or only the\none given by `storeName`. Typical use: enumerate what a remote WMS server registered with\n`QUERY.addRemoteWMSServer` publishes, to add its layers to the map.\n@param storeName {String} Source id to restrict the listing to (see `getLayerSourceNames`); omit for all sources.",
        "type": "function(storeName) : Array.<String>",
        "qualifiedName": "ExtjsUtils.LAYER.getStoreLayersNames",
        "examples": [
          "ExtjsUtils.LAYER.getStoreLayersNames('remote').forEach(function(name) {\n  ExtjsUtils.QUERY.addLayer({ name: name, title: name, source: 'remote', visibility: false });\n});"
        ],
        "returnDescription": "The layer names (`'workspace:layer'`)."
      },
      {
        "key": "getVisibleLayers",
        "description": "Returns the visible WMS layers of the map (see `getLocalLayers`), by default ordered from the\ntopmost layer down — i.e. the order in which they may be under the mouse.\n@param mapStores {Array.<OpenLayers.Layer>} Layers to search; defaults to all layers of the map.\n@param sortTop {Boolean} `true`/omitted to sort the result from the highest to the lowest `z-index`; `false` to keep the map order.",
        "type": "function(mapStores, sortTop) : Array.<OpenLayers.Layer>",
        "qualifiedName": "ExtjsUtils.LAYER.getVisibleLayers",
        "examples": [
          "var topLayer = ExtjsUtils.LAYER.getVisibleLayers()[0];"
        ],
        "returnDescription": "The visible layers, topmost first unless `sortTop` is `false`."
      },
      {
        "key": "isBackgroundLayerOrRecord",
        "description": "Tells whether a layer or a layer record is a background map. Mappia basemaps are the layers\ndeclared with `group: \"background\"` (checked on the layer, on its `json` definition and on\nthe record data); plain OpenLayers base layers (`isBaseLayer`) also count.\n@param recordOrLayer {OpenLayers.Layer|GeoExt.data.LayerRecord} The layer or layer record to test.",
        "type": "function(recordOrLayer) : Boolean",
        "qualifiedName": "ExtjsUtils.LAYER.isBackgroundLayerOrRecord",
        "examples": [
          "var overlays = app.mapPanel.map.layers.filter(function(layer) {\n  return !ExtjsUtils.LAYER.isBackgroundLayerOrRecord(layer);\n});"
        ],
        "returnDescription": "`true` for a background map, `false` otherwise (also for `null`/`undefined`)."
      },
      {
        "key": "isOnHover",
        "description": "Informs if the mouse is over the current layer.\n@param layer {Layer|Composer} Layer to check if the mouse is over.\n@param mouseEvt {Event} The mouse event.",
        "type": "function(layer, mouseEvt) : Boolean",
        "qualifiedName": "ExtjsUtils.LAYER.isOnHover",
        "returnDescription": "Returns true if the mouse is over the 'layer', False otherwise."
      },
      {
        "key": "layerExists",
        "description": "Tells whether some source offers a layer with the given full name — i.e. whether\n`getLayersRecords(layerName)` finds a record. Useful to guard `QUERY.addLayer` against a\nname that is not published by any server.\n@param layerName {String} Full layer name, e.g. `'CSR:estados'`.",
        "type": "function(layerName) : Boolean",
        "qualifiedName": "ExtjsUtils.LAYER.layerExists",
        "examples": [
          "if (ExtjsUtils.LAYER.layerExists('CSR:estados')) ExtjsUtils.QUERY.addLayer({ name: 'CSR:estados', title: 'States' });"
        ],
        "returnDescription": "`true` when a source has the layer, `false` otherwise."
      },
      {
        "key": "layerSourceRequireCORS",
        "description": "Tells whether the source a layer comes from was registered with `cors: true` — i.e. its\ntiles must be fetched through the CORS proxy before their pixels can be read (remote WMS\nservers added with `QUERY.addRemoteWMSServer`).\n@param layerNameID {String} Full layer name (`'workspace:layer'`).",
        "type": "function(layerNameID) : Boolean",
        "qualifiedName": "ExtjsUtils.LAYER.layerSourceRequireCORS",
        "examples": [
          "// 'myserver' being a source registered with QUERY.addRemoteWMSServer\nif (ExtjsUtils.LAYER.layerSourceRequireCORS('myserver:landuse')) console.log('tiles go through the CORS proxy');"
        ],
        "returnDescription": "`true` when the layer source requires CORS, `false` otherwise (also when the layer is in no source)."
      },
      {
        "key": "loadWait",
        "description": "Loads a URL while making a layer wait for it: the layer's `startLoadingResource` event is\nfired before the request and `endLoadingResource` after it, so the layer's calculations are\non hold until the resource arrives. The `success`/`failure` callbacks run before the layer is\nnotified. Use it from a layer's `beforeCalc` (or similar hooks) to fetch data the layer\nexpression depends on.\n@param layer {OpenLayers.Layer} Layer that must wait for the resource.\n@param url {String} URL to load.\n@param success {function} Called with the response (`XMLHttpRequest`) on success.\n@param failure {function} Called on failure (no arguments).\n@param useCors {Boolean} `true` when the URL is on another host, to fetch it through the CORS proxy (`REQUEST.getCORS`) instead of `REQUEST.get`.",
        "type": "function(layer, url, success, failure, useCors)",
        "qualifiedName": "ExtjsUtils.LAYER.loadWait",
        "examples": [
          "ExtjsUtils.LAYER.loadWait(this, '/wmtp/data/thresholds.json', function(resp) {\n  window.thresholds = JSON.parse(resp.responseText);\n}, function() {\n  ExtjsUtils.ALERTIFY.log('Could not load the thresholds');\n});"
        ]
      },
      {
        "key": "sortTopLayers",
        "description": "Sorts an array of layers so the ones drawn on top come first (decreasing `z-index` of the\nlayer `div`). The array is sorted in place and also returned.\n@param layersArray {Array.<OpenLayers.Layer>} Layers to sort, e.g. the result of `getLocalLayers`.",
        "type": "function(layersArray) : Array.<OpenLayers.Layer>",
        "qualifiedName": "ExtjsUtils.LAYER.sortTopLayers",
        "examples": [
          "var topFirst = ExtjsUtils.LAYER.sortTopLayers(ExtjsUtils.LAYER.getLocalLayers(app.mapPanel.map.layers));"
        ],
        "returnDescription": "The same array, ordered from the topmost layer to the bottom one."
      },
      {
        "key": "tileSize",
        "description": "Expected size, in pixels, of the square tiles a WMS layer is drawn with (`256`). Used by the\npixel-reading helpers (`getLayerTilesFeatureIntersects`) to walk a layer's tile images.",
        "type": "Number",
        "qualifiedName": "ExtjsUtils.LAYER.tileSize",
        "examples": [
          "var pixelsPerTile = ExtjsUtils.LAYER.tileSize * ExtjsUtils.LAYER.tileSize;"
        ]
      },
      {
        "key": "zoomMapsExtents",
        "description": "Zooms the map to the smallest extent containing every query layer: the union of the\nextents (`getLayerExtent`) of the `local`, `file` and `remote` layers that have a bounding\nbox, skipping background layers and — unless `includeHidden` is `true` — hidden layers.\nNothing happens when no layer qualifies. Commonly called from `onToggleViewGroup` to\nre-frame the map when a group is expanded.\n@param includeHidden {Boolean} `true` to also count layers that are not visible.\n@param closest {Boolean} `true` to zoom as close as possible even if parts of the extent fall outside the viewport; `false`/omitted to fit the whole extent on screen.",
        "type": "function(includeHidden, closest) : Array.<Number>",
        "qualifiedName": "ExtjsUtils.LAYER.zoomMapsExtents",
        "examples": [
          "// In a group definition: re-frame the map on the visible layers when the group opens\nonToggleViewGroup: function(view) {\n  if (view.expanded) ExtjsUtils.LAYER.zoomMapsExtents(false);\n}"
        ],
        "returnDescription": "The union extent as `[left, bottom, right, top]` in the map projection (all zeros when no layer qualified)."
      }
    ],
    "LEGENDS": [
      {
        "key": "_information",
        "description": "Legend lookup helpers.\nUsage: ExtjsUtils.LEGENDS",
        "name": "LEGENDS · legend lookup"
      },
      {
        "key": "getColorTitle",
        "description": "Finds the legend entry of a layer whose colour is exactly `(r, g, b)` and returns its\ntitle — the way to map a pixel colour read from the map back to the legend class it\nrepresents. Works on layers exposing `getLegendEntries()` (composed/calculation layers).\n@param layer {OpenLayers.Layer} Layer whose legend entries are searched.\n@param r {Number} Red component of the colour (0-255).\n@param g {Number} Green component of the colour (0-255).\n@param b {Number} Blue component of the colour (0-255).",
        "type": "function(layer, r, g, b) : String|null",
        "qualifiedName": "ExtjsUtils.LEGENDS.getColorTitle",
        "examples": [
          "var rgba = ExtjsUtils.LAYER.getLayerColorAtPoint(layer.layers[0], ExtjsUtils.HTML.getAbsolutePointFromMouseEvent(evt));\nif (rgba) console.log('Class under the mouse:', ExtjsUtils.LEGENDS.getColorTitle(layer, rgba[0], rgba[1], rgba[2]));"
        ],
        "returnDescription": "The title of the legend entry with that colour, or `null` when no entry matches."
      }
    ],
    "Number": [
      {
        "key": "_information",
        "description": "Format and abbreviate numbers. Usage: ExtjsUtils.NUMBER",
        "name": "NUMBER · format numbers"
      },
      {
        "key": "abbreviateNumber",
        "description": "Auxiliary function to shorten the display of numbers.\n\nPS: The number loses a little precision but becomes more meaningful.\n@param value {Number} Value to be displayed.\n@param useFixed {Number} If set it will limit the number of decimal places, otherwise it will use the default value.\n@param abbreviateNumbers {Array.<Number>} Number suffix name.",
        "type": "function(value, useFixed, abbreviateNumbers) : Number|String",
        "qualifiedName": "ExtjsUtils.NUMBER.abbreviateNumber",
        "returnDescription": "Simplified value to be displayed. Can return a number or a string."
      },
      {
        "key": "numericToString",
        "description": "Formats a number for display with `toLocaleString()` (thousands separator and decimal\nmark). It was written to follow the interface language (`pt-br` or `en-IN`), but the\ncurrent implementation compares the value itself with the language codes, so for a\nnumber it always falls back to the browser's default locale.\n@param value {Number} Value to be represented as a string.",
        "type": "function(value) : String",
        "qualifiedName": "ExtjsUtils.NUMBER.numericToString",
        "examples": [
          "ExtjsUtils.NUMBER.numericToString(1234567.5); // \"1.234.567,5\" in a pt-BR browser"
        ],
        "returnDescription": "The localized string representation of the value."
      }
    ],
    "OBSERVABILITY": [
      {
        "key": "_information",
        "description": "Structured console events for diagnostics.\nUsage: ExtjsUtils.OBSERVABILITY",
        "name": "OBSERVABILITY · console events"
      },
      {
        "key": "event",
        "description": "Logs a structured diagnostic event to the console as `[MappiaObservability] {event, ts,\npage_origin, payload}` — with `console.error` when `level` is `\"error\"`, `console.warn`\notherwise — and forwards the same entry to `window.__mappiaObservabilityHook(entry)` when the\nhost page defines that function. Useful to report tenant-side failures in a way the\nembedding site can collect.\n@param eventName {String} Dotted event name, e.g. `\"mappia.fetch.cors_blocked\"`.\n@param payload {Object} Extra data attached to the entry (defaults to `{}`).\n@param level {String} `\"error\"` to log with `console.error`; anything else logs a warning.",
        "type": "function(eventName, payload, level)",
        "qualifiedName": "ExtjsUtils.OBSERVABILITY.event",
        "examples": [
          "ExtjsUtils.OBSERVABILITY.event(\"myquery.csv.load_failed\", { url: csvUrl }, \"error\");"
        ]
      },
      {
        "key": "installFetchFailureHook",
        "description": "Installs (once) a `window` \"unhandledrejection\" listener that turns unhandled `fetch`\nfailures whose message mentions \"failed to fetch\", \"cors\" or \"network error\" into an\n`OBSERVABILITY.event(\"mappia.fetch.cors_blocked\", {message, page_origin, hint}, \"error\")`\nentry, so CORS/network problems of cross-origin requests show up with a hint instead of a\nsilent rejection. The platform calls it at start-up; calling it again is a no-op.",
        "type": "function()",
        "qualifiedName": "ExtjsUtils.OBSERVABILITY.installFetchFailureHook",
        "examples": [
          "ExtjsUtils.OBSERVABILITY.installFetchFailureHook();"
        ]
      }
    ],
    "REQ_PARAMS": [
      {
        "key": "_information",
        "description": "Parsers for URL parameters with their own semantics (visiblelayers).\nUsage: ExtjsUtils.REQ_PARAMS",
        "name": "REQ_PARAMS · URL parameter parsers"
      },
      {
        "key": "getQntVisibleLayers",
        "description": "Reads the `visiblelayers` URL parameter, which overrides how many layers of the query start\nvisible (see `URLProperties.visiblelayers`). Values: a positive `N` shows the first N layers\nin query order (the top of the layer list); a negative `-N` shows the last N (the bottom of\nthe list); `0` shows none; `custom` keeps the `visibility` each layer defines in the query\n(returns null). When the parameter is omitted the result is `1` if the URL has\n`options=onlyfirstvisible`, otherwise null (the query's own visibilities apply).",
        "type": "function() : Number|null",
        "qualifiedName": "ExtjsUtils.REQ_PARAMS.getQntVisibleLayers",
        "examples": [
          "// ?visiblelayers=-2  -> the last two layers of the query start visible\nvar qnt = ExtjsUtils.REQ_PARAMS.getQntVisibleLayers(); // -2"
        ],
        "returnDescription": "Number of layers to show (positive from the top, negative from the bottom), or null to keep the query's own visibilities."
      }
    ],
    "Request": [
      {
        "key": "_information",
        "description": "The page address (its parameters and options=) and HTTP requests. Usage: ExtjsUtils.REQUEST",
        "name": "REQUEST · page address and network"
      },
      {
        "key": "CORS_URL",
        "description": "Path of the server-side CORS proxy without caching (`/cors/direct/`). Prefix a\ncross-origin URL with it when the response must always be fresh.",
        "type": "String",
        "qualifiedName": "ExtjsUtils.REQUEST.CORS_URL",
        "examples": [
          "ExtjsUtils.REQUEST.get(ExtjsUtils.REQUEST.CORS_URL + \"https://example.org/live.json\", onOk, onFail);"
        ]
      },
      {
        "key": "CORS_URL_CACHED",
        "description": "Path of the server-side CORS proxy with caching (`/cors/`); `getCORS` prefixes the\ntarget URL with it. Use it to fetch a cross-origin resource that may be cached.",
        "type": "String",
        "qualifiedName": "ExtjsUtils.REQUEST.CORS_URL_CACHED",
        "examples": [
          "ExtjsUtils.REQUEST.get(ExtjsUtils.REQUEST.CORS_URL_CACHED + \"https://example.org/data.json\", onOk, onFail);"
        ]
      },
      {
        "key": "Response",
        "description": "Constructor of the small object a file layer passes to its `endLoadingLayer` event\n(`new ExtjsUtils.REQUEST.Response(success, url)`), wrapping the outcome of the data\nrequest. Instances expose `getSuccess()` (Boolean, true when the request succeeded) and\n`getUrl()` (String, the requested URL). Tenant code normally only reads it in the\nevent listener.\n@param success {Boolean} True when the request succeeded, false on error.\n@param url {String} URL of the request.",
        "type": "function(success, url)",
        "qualifiedName": "ExtjsUtils.REQUEST.Response",
        "examples": [
          "layer.events.on({endLoadingLayer: function(response) {\n    if (!response.getSuccess()) ExtjsUtils.ALERTIFY.log(\"Failed to load \" + response.getUrl());\n}});"
        ]
      },
      {
        "key": "abort",
        "description": "Aborts every unfinished abortable request (`getAbortable`/`getAbortables`) started with\nthe given scope. The `failed` callback of each aborted request is invoked with\n`wasAborted = true`.\n@param scope {Object} The scope object the requests were registered with.",
        "type": "function(scope) : Number",
        "qualifiedName": "ExtjsUtils.REQUEST.abort",
        "examples": [
          "var owner = {};\nExtjsUtils.REQUEST.getAbortable(\"/theme/app/data/totals.csv\", onOk, onFail, false, owner);\n// later, e.g. when the user changes the selection:\nExtjsUtils.REQUEST.abort(owner);"
        ],
        "returnDescription": "How many requests were aborted."
      },
      {
        "key": "createRequestObject",
        "description": "Builds the request description consumed by `getAbortables`: an object exposing\n`getURL()`, `getSuccess()`, `getFailed()`, `getForceSynchronous()` and `getScope()`.\nAt least one of the two callbacks must be given.\n@param url {String} URL of the request.\n@param success {function} Success callback, called as `success.call(scope, xhr, wasAborted)`.\n@param failed {function} Failure callback, called as `failed.call(scope, xhr, wasAborted)`.\n@param forceSynchronous {Boolean} True (deprecated) to make the request synchronous.\n@param scope {Object} `this` for the callbacks; also the scope used by `abort`.",
        "type": "function(url, success, failed, forceSynchronous, scope) : Object",
        "qualifiedName": "ExtjsUtils.REQUEST.createRequestObject",
        "examples": [
          "var req = ExtjsUtils.REQUEST.createRequestObject(\"/theme/app/data/totals.csv\", function(xhr) {\n    this.totals = xhr.responseText;\n}, null, false, this);"
        ],
        "returnDescription": "The request object to pass to `getAbortables`."
      },
      {
        "key": "downloadFile",
        "description": "Triggers a browser download of a file: creates a temporary `<a download target=\"_blank\">`\npointing at the URL, clicks it and removes it. The suggested file name is the last\nsegment of the URL. Typical use is a \"download the data\" button in a query.\n@param fileUrl {String} URL of the file to download.",
        "type": "function(fileUrl)",
        "qualifiedName": "ExtjsUtils.REQUEST.downloadFile",
        "examples": [
          "ExtjsUtils.REQUEST.downloadFile(\"/theme/app/data/example_fire_history.csv\");"
        ]
      },
      {
        "key": "get",
        "description": "Plain AJAX GET request. The usual way for query code to fetch a CSV/JSON/text resource\nfrom the Mappia server (`/theme/app/data/...`, a WFS GetFeature on the local GeoServer)\nor from any server that allows the page origin (CORS); cookies are sent on\ncross-origin calls. For servers without CORS headers use `getCORS`. Both callbacks\nreceive the XMLHttpRequest as their only argument — read `xhr.responseText`.\n@param url {String} URL to load (relative to the page or absolute, including the protocol).\n@param success {function} Called with the XMLHttpRequest when the status is 200.\n@param failed {function} Called with the XMLHttpRequest on any other status.\n@param forceSynchronous {Boolean} True to make the request synchronous (deprecated), false for asynchronous.\n@param scope {Object} `this` for the `success` and `failed` callbacks.",
        "type": "function(url, success, failed, forceSynchronous, scope) : XMLHttpRequest",
        "qualifiedName": "ExtjsUtils.REQUEST.get",
        "examples": [
          "var url = ExtjsUtils.REQUEST.getGeoserverBaseUrl() + \"/wfs?service=WFS&version=1.0.0&request=GetFeature\" +\n    \"&typename=CSR:example_layer&outputFormat=json&CQL_FILTER=code='\" + code + \"'\";\nExtjsUtils.REQUEST.get(url, function(xhr) {\n    var features = ExtjsUtils.GEOJSON.geojson2Features(JSON.parse(xhr.responseText));\n    ExtjsUtils.ZOOM.zoomToExtent(features[0].geometry.getBounds());\n}, function(xhr) {\n    ExtjsUtils.ALERTIFY.alert(\"Request failed: \" + xhr.status);\n});"
        ],
        "returnDescription": "The request object (can be aborted with `xhr.abort()`)."
      },
      {
        "key": "getAbortable",
        "description": "Same as `get`, but the request is registered under `scope` so it can be cancelled later\nwith `abort(scope)`. The callbacks receive `(xhr, wasAborted)`; when the request is\naborted the `failed` callback runs with `wasAborted = true`. A scope is mandatory.\n@param url {String} URL to load.\n@param success {function} Called as `success.call(scope, xhr, wasAborted)` when the status is 200.\n@param failed {function} Called as `failed.call(scope, xhr, wasAborted)` on any other status or on abort.\n@param forceSynchronous {Boolean} True (deprecated) to make the request synchronous.\n@param scope {Object} Object identifying the owner of the request (used by `abort`) and `this` for the callbacks.",
        "type": "function(url, success, failed, forceSynchronous, scope) : XMLHttpRequest",
        "qualifiedName": "ExtjsUtils.REQUEST.getAbortable",
        "examples": [
          "var owner = {};\nExtjsUtils.REQUEST.getAbortable(\"/theme/app/data/totals.csv\", function(xhr) {\n    showTotals(xhr.responseText);\n}, function(xhr, wasAborted) {\n    if (!wasAborted) ExtjsUtils.ALERTIFY.log(\"Could not load the totals.\");\n}, false, owner);\n// ExtjsUtils.REQUEST.abort(owner) cancels it."
        ],
        "returnDescription": "The request object."
      },
      {
        "key": "getAbortables",
        "description": "Runs a group of abortable GET requests (built with `createRequestObject`) and calls\n`afterAll` once every one of them has finished (success, failure or abort). Each\nrequest's own callbacks run first; `afterAll` receives the arguments of the last\nrequest to finish (`[xhr, wasAborted]`).\n@param requestObjects {Array.<Object>|Object} Request objects from `createRequestObject` (a single object is accepted).\n@param afterAll {function} Called after the last request finishes.\n@param afterScope {Object} `this` for `afterAll`; defaults to the scope of the last finished request.",
        "type": "function(requestObjects, afterAll, afterScope) : Array.<XMLHttpRequest>",
        "qualifiedName": "ExtjsUtils.REQUEST.getAbortables",
        "examples": [
          "var owner = {};\nExtjsUtils.REQUEST.getAbortables([\n    ExtjsUtils.REQUEST.createRequestObject(\"/theme/app/data/a.csv\", function(xhr) { this.a = xhr.responseText; }, null, false, owner),\n    ExtjsUtils.REQUEST.createRequestObject(\"/theme/app/data/b.csv\", function(xhr) { this.b = xhr.responseText; }, null, false, owner)\n], function() {\n    buildChart(owner.a, owner.b);\n});"
        ],
        "returnDescription": "The request objects that were started."
      },
      {
        "key": "getBaseMappia",
        "description": "Returns the base URL of the Mappia server for platform endpoints such as\n`/query/description/<id>/`: an empty string (relative URLs) when the page is served from\n`maps.csr.ufmg.br` without a `?geoserver=` override, otherwise the absolute\n`https://maps.csr.ufmg.br`. Not configurable at the moment.",
        "type": "function() : String",
        "qualifiedName": "ExtjsUtils.REQUEST.getBaseMappia",
        "examples": [
          "ExtjsUtils.REQUEST.get(ExtjsUtils.REQUEST.getBaseMappia() + \"/query/description/1/\", onLoad);"
        ],
        "returnDescription": "`\"\"` or `\"https://maps.csr.ufmg.br\"`."
      },
      {
        "key": "getCORS",
        "description": "GET request to a cross-origin URL through the platform's cached CORS proxy\n(`CORS_URL_CACHED + url`), for servers that do not send CORS headers. Both callbacks\nreceive the XMLHttpRequest (read `responseText`). Ext's Ajax cannot be used for this\nbecause it adds an `X-Requested-With` header that breaks such requests.\n@param url {String} Full URL to load, including the protocol (`https://...`).\n@param success {function} Called with the XMLHttpRequest when the status is 200.\n@param failed {function} Called with the XMLHttpRequest on any other status.\n@param forceSynchronous {Boolean} True to make the request synchronous (deprecated).",
        "type": "function(url, success, failed, forceSynchronous) : XMLHttpRequest",
        "qualifiedName": "ExtjsUtils.REQUEST.getCORS",
        "examples": [
          "ExtjsUtils.REQUEST.getCORS(\"https://example.org/api/property?code=\" + code, function(xhr) {\n    var data = JSON.parse(xhr.responseText);\n}, function(xhr) {\n    ExtjsUtils.ALERTIFY.log(\"Could not load the property data.\");\n});"
        ],
        "returnDescription": "The request object."
      },
      {
        "key": "getCommaSeparatedArguments",
        "description": "Reads a URL parameter that holds a comma-separated list and returns its items — the\nway `?options=`, `?tools=` and custom list parameters (`?remotemap=a,b,c`) are read.\n@param paramName {String} Name of the URL parameter.",
        "type": "function(paramName) : Array.<String>",
        "qualifiedName": "ExtjsUtils.REQUEST.getCommaSeparatedArguments",
        "examples": [
          "// page opened as /calculator/?queryid=1&points=A,B,C\nExtjsUtils.REQUEST.getCommaSeparatedArguments(\"points\"); // [\"A\", \"B\", \"C\"]"
        ],
        "returnDescription": "The items of the list; an empty array when the parameter is absent or empty."
      },
      {
        "key": "getGeoserverBaseUrl",
        "description": "Returns the GeoServer base URL in use by the `local` source (from the `?geoserver=` URL\nparameter, default `/geoserver`), without a trailing slash — use it to build WFS/WMS\nrequests against the same server the map layers come from.",
        "type": "function() : String",
        "qualifiedName": "ExtjsUtils.REQUEST.getGeoserverBaseUrl",
        "examples": [
          "var wfs = ExtjsUtils.REQUEST.getGeoserverBaseUrl() + \"/wfs?service=WFS&version=1.0.0&request=GetFeature&typename=CSR:example&outputFormat=json\";"
        ],
        "returnDescription": "The GeoServer base URL."
      },
      {
        "key": "getHost",
        "description": "Returns the origin of the current page — protocol plus host, without a trailing slash\n(e.g. `'https://maps.csr.ufmg.br'`) — for building absolute URLs to the same server.",
        "type": "function() : String",
        "qualifiedName": "ExtjsUtils.REQUEST.getHost",
        "examples": [
          "var wfs = ExtjsUtils.REQUEST.getHost() + \"/geoserver/wfs?service=WFS&request=GetFeature\";"
        ],
        "returnDescription": "The page protocol and host."
      },
      {
        "key": "getMapObject",
        "description": "Builds a layer configuration object from the compact `name;style;opacity` string used by\nthe `?map=` URL parameter (see `URLProperties.map`): `{name, source, styles, opacity}`\nwith `styles`/`opacity` present only when given (both URL-decoded).\n@param curDefinition {String} Layer definition such as `CSR:example_layer;example_style;0.8`.\n@param source {String} Name of the layer source; the default `local` source when empty.",
        "type": "function(curDefinition, source) : Object",
        "qualifiedName": "ExtjsUtils.REQUEST.getMapObject",
        "examples": [
          "ExtjsUtils.REQUEST.getMapObject(\"CSR:example_layer;example_style\");\n// {name: \"CSR:example_layer\", source: \"local\", styles: \"example_style\"}"
        ],
        "returnDescription": "A layer configuration ready for `QUERY.addLayer`."
      },
      {
        "key": "getParameterByName",
        "description": "Reads a parameter of the page URL query string (`?name=value`), URL-decoded, with `+`\nturned into spaces. This is the standard way for a query to receive external input\n(a property code, a language, a colour) from the embedding page. The name match is\ncase-insensitive.\n@param name {String} Name of the URL parameter.",
        "type": "function(name) : String",
        "qualifiedName": "ExtjsUtils.REQUEST.getParameterByName",
        "examples": [
          "// page opened as /calculator/?queryid=1&car=MG-1234567-ABCD\nvar car = ExtjsUtils.REQUEST.getParameterByName(\"car\"); // \"MG-1234567-ABCD\"\nif (car) loadProperty(car);"
        ],
        "returnDescription": "The decoded value, or an empty string when the parameter is absent."
      },
      {
        "key": "getProtocol",
        "description": "Returns the protocol of the current page, including the colon (`'http:'` or `'https:'`).",
        "type": "function() : String",
        "qualifiedName": "ExtjsUtils.REQUEST.getProtocol",
        "examples": [
          "var url = ExtjsUtils.REQUEST.getProtocol() + \"//maps.csr.ufmg.br/geoserver/wms\";"
        ],
        "returnDescription": "The page protocol."
      },
      {
        "key": "getRelativeRootUri",
        "description": "Resolves a path against the application root: takes the current page URL, strips the\n`/calculator/` or `/editor/` segment (and any `.html` file name) and appends\n`relativeUrl`. Use it to load platform assets (`/theme/app/...`) from a page that lives\nin a sub-folder or on another host.\n@param relativeUrl {String} Path relative to the application root (leading slash optional).",
        "type": "function(relativeUrl) : String",
        "qualifiedName": "ExtjsUtils.REQUEST.getRelativeRootUri",
        "examples": [
          "ExtjsUtils.REQUEST.getRelativeRootUri(\"/theme/app/js/jsts.min.js\"); // \"https://maps.csr.ufmg.br/theme/app/js/jsts.min.js\""
        ],
        "returnDescription": "The absolute URL of the resource."
      },
      {
        "key": "isEditor",
        "description": "Tells whether the current page is the query editor (`/editor/`) rather than the viewer\n(`/calculator/`), so query code can skip viewer-only decoration while being edited.",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.REQUEST.isEditor",
        "examples": [
          "if (!ExtjsUtils.REQUEST.isEditor()) ExtjsUtils.QUERY.decorate({noTop: true});"
        ],
        "returnDescription": "True when the page is the editor, false otherwise."
      },
      {
        "key": "isHttps",
        "description": "Tells whether the page is being served over HTTPS.",
        "type": "function() : Boolean",
        "qualifiedName": "ExtjsUtils.REQUEST.isHttps",
        "examples": [
          "var scheme = ExtjsUtils.REQUEST.isHttps() ? \"https://\" : \"http://\";"
        ],
        "returnDescription": "True when the page protocol is `https:`, false otherwise."
      },
      {
        "key": "isLocalUrl",
        "description": "Identify if the 'url' is from the localhost or is from the maps.csr.ufmg.br domain.\n\nPS: If url is left blank it uses the current site url.\n@param url {String} String with the url to be checked.",
        "type": "function(url) : Boolean",
        "qualifiedName": "ExtjsUtils.REQUEST.isLocalUrl",
        "examples": [
          "ExtjsUtils.REQUEST.isLocalUrl() // true",
          "ExtjsUtils.REQUEST.isLocalUrl(\"www.google.com\") // false"
        ],
        "returnDescription": "Returns true if the url is from localhost, otherwise it returns false."
      },
      {
        "key": "mapHostGeoserverUrl",
        "description": "Forces a `/geoserver/...` GetMap (or similar) URL onto the map iframe's configured\nGeoServer (`?geoserver=` / `getGeoserverBaseUrl()`, default `/geoserver` on this origin).\nCapabilities OnlineResource often names `https://maps.csr.ufmg.br/geoserver/...` even when\nthe calculator runs on another host — live tiles and OfflineAreas cache keys must use the\nmap host, not that external absolute URL. Non-`/geoserver` URLs (OSM, remote WMS, GitHub\nstores) are returned unchanged.\n@param url {String} Absolute or relative WMS URL.",
        "type": "function(url) : String",
        "qualifiedName": "ExtjsUtils.REQUEST.mapHostGeoserverUrl",
        "examples": [
          "ExtjsUtils.REQUEST.mapHostGeoserverUrl(\"https://maps.csr.ufmg.br/geoserver/wms?REQUEST=GetMap\");\n// on https://localhost → \"https://localhost/geoserver/wms?REQUEST=GetMap\""
        ],
        "returnDescription": "URL under the map's GeoServer base, or the original URL."
      },
      {
        "key": "mixedContentUrlFix",
        "description": "Rewrites an `http://maps.csr.ufmg.br` (or `http://localhost`) URL to the protocol and\nhost of the current page, avoiding mixed-content and CORS errors when the page is served\nover HTTPS. Other URLs are returned unchanged.\n@param url {String} URL of the request.",
        "type": "function(url) : String",
        "qualifiedName": "ExtjsUtils.REQUEST.mixedContentUrlFix",
        "examples": [
          "ExtjsUtils.REQUEST.mixedContentUrlFix(\"http://maps.csr.ufmg.br/geoserver/wms\"); // \"https://maps.csr.ufmg.br/geoserver/wms\" on an https page"
        ],
        "returnDescription": "The URL using the same protocol/host as the page."
      },
      {
        "key": "onRequestError",
        "description": "Generic failure handler for requests: shows a modal `Ext.Msg.alert` with the server's\n`responseText`, or the \"server unreachable\" message when there is none. Pass it as the\n`failed` callback of `get`/`post` when a plain error dialog is enough.\n@param response {Object} Response object (XMLHttpRequest or Ext response) of the failed request, when available.",
        "type": "function(response)",
        "qualifiedName": "ExtjsUtils.REQUEST.onRequestError",
        "examples": [
          "ExtjsUtils.REQUEST.get(\"/theme/app/data/totals.csv\", onOk, ExtjsUtils.REQUEST.onRequestError);"
        ]
      },
      {
        "key": "post",
        "description": "AJAX POST of a form-encoded body: `params` is serialized with `encodeURIComponent` as\n`application/x-www-form-urlencoded`. Cookies are sent on cross-origin calls. Both\ncallbacks receive the XMLHttpRequest — typical use is sending a clicked coordinate or a\nform to an external report service from a layer `onClick` handler.\n@param url {String} URL to post to (relative or absolute, including the protocol).\n@param params {Object} Key/value pairs sent as the request body.\n@param success {function} Called with the XMLHttpRequest when the status is 200.\n@param failed {function} Called with the XMLHttpRequest on any other status.\n@param forceSynchronous {Boolean} True (deprecated) to make the request synchronous.\n@param scope {Object} `this` for the callbacks.",
        "type": "function(url, params, success, failed, forceSynchronous, scope)",
        "qualifiedName": "ExtjsUtils.REQUEST.post",
        "examples": [
          "ExtjsUtils.REQUEST.post(\"https://example.org/report.php\", {lat: lonLat.lat, lon: lonLat.lon}, function(xhr) {\n    document.getElementById(\"report\").innerHTML = xhr.responseText;\n}, function(xhr) {\n    ExtjsUtils.ALERTIFY.log(\"Report service unavailable.\");\n}, false, this);"
        ]
      },
      {
        "key": "removeHTTPProtocol",
        "description": "Strips a leading `http:` from a URL, turning it into a protocol-relative URL\n(`//host/path`) that follows the protocol of the page and avoids mixed-content blocks.\n@param url {String} URL to rewrite.",
        "type": "function(url) : String",
        "qualifiedName": "ExtjsUtils.REQUEST.removeHTTPProtocol",
        "examples": [
          "ExtjsUtils.REQUEST.removeHTTPProtocol(\"http://maps.csr.ufmg.br/geoserver/wms\"); // \"//maps.csr.ufmg.br/geoserver/wms\""
        ],
        "returnDescription": "The URL without the `http:` prefix (unchanged when it had none)."
      },
      {
        "key": "setGeoserverBaseUrl",
        "description": "Sets the base URL of the GeoServer behind the `local` layer source (its WMS endpoint\nbecomes `url + \"/wms\"`). The platform calls it at startup with the `?geoserver=` URL\nparameter, defaulting to `/geoserver`; query code rarely needs it. The URL must not end\nwith a slash nor point at a service (`.../wms`), otherwise an error is logged.\n@param url {String} GeoServer base URL, absolute (`https://maps.csr.ufmg.br/geoserver`) or relative to the root (`/geoserver`).",
        "type": "function(url)",
        "qualifiedName": "ExtjsUtils.REQUEST.setGeoserverBaseUrl",
        "examples": [
          "ExtjsUtils.REQUEST.setGeoserverBaseUrl(\"https://maps.csr.ufmg.br/geoserver\");"
        ]
      },
      {
        "key": "tryHttps",
        "description": "Redirects the page to its HTTPS version once: the attempt is remembered in\n`COOKIE.TRY_HTTPS`, so when the browser cannot reach the HTTPS site and comes back\nover HTTP it is not redirected again. Does nothing when already on HTTPS.",
        "type": "function()",
        "qualifiedName": "ExtjsUtils.REQUEST.tryHttps",
        "examples": [
          "ExtjsUtils.REQUEST.tryHttps();"
        ]
      },
      {
        "key": "urlOptions",
        "description": "Tells whether a token is present in the `options` URL parameter, the comma-separated\nlist of advanced page options (`?options=scale,startopened,hidestylechooser`, see\n`URLOptions`). The real call is `ExtjsUtils.REQUEST.advToolsOptions(\"token\")`; query\ncode uses it to react to custom tokens of its own.\n@param property {String} Option token to look for (exact, case-sensitive match).",
        "type": "function(property) : Boolean",
        "examples": [
          "// page opened as /calculator/?queryid=1&options=scale,startopened,compact\nExtjsUtils.REQUEST.advToolsOptions(\"startopened\"); // true\nif (ExtjsUtils.REQUEST.advToolsOptions(\"compact\")) ExtjsUtils.QUERY.decorate({noTop: true});"
        ],
        "returnDescription": "True when the token is present in `?options=`, false otherwise."
      }
    ],
    "TooltipHelper": [
      {
        "key": "_information",
        "completeAPI": "View the complete TooltipHelper <a href='/api/#category_TooltipHelper'>API here</a>.",
        "description": "Functions to create tooltips on elements, at fixed positions and at mouse events.\nUsage: ExtjsUtils.TooltipHelper",
        "name": "TooltipHelper · tooltips"
      },
      {
        "key": "CreateTooltipOnPosition",
        "description": "Create a tooltip at a fixed position.\n\nIf the position is the mouse event, the tooltip will be at the right side and below the pointer.\nIf the position is a array[x,y] will anchor the top left at this point.\n@param title {String} Tooltip title.\n@param contentHtml {String} Tooltip HTML content.\n@param position {Event|Array.<Number>} Defines the top left\nposition of the tooltip. Can be either a mouse event at or an\narray [x:Number, y:Number] with the cursor position.\n@param additionalConfig {Object} Additional configuration for the tooltip.",
        "type": "function(title, contentHtml, position, additionalConfig) : Ext.Tooltip",
        "qualifiedName": "ExtjsUtils.TooltipHelper.CreateTooltipOnPosition",
        "returnDescription": "A tooltip anchored at the given position."
      },
      {
        "key": "CreateTooltipThumb",
        "description": "Creates a mouse-tracking `Ext.ToolTip` on a DOM element with a title, a text description\n(rendered in a `<pre>`) and an optional thumbnail image. The tooltip ignores mouse\nevents, so it never interferes with the hovered element, and is repositioned by\n`onMoveFixPosition` to stay beside the target and inside the viewport. The description\nis cut at the first sequence of three line breaks.\n@param title {String} Tooltip title (may be empty).\n@param description {String} Tooltip text.\n@param target {HTMLElement} Element that shows the tooltip on hover (the DOM element itself, not an id). Without it the tooltip is created unattached.\n@param thumbUrl {String} URL of an image shown above the text.\n@param showDelay {Number} Delay in milliseconds before the tooltip appears.",
        "type": "function(title, description, target, thumbUrl, showDelay) : Ext.ToolTip",
        "qualifiedName": "ExtjsUtils.TooltipHelper.CreateTooltipThumb",
        "examples": [
          "var node = document.getElementById(\"field_area\");\nExtjsUtils.TooltipHelper.CreateTooltipThumb(\"\", \"Area of the property in hectares.\", node);"
        ],
        "returnDescription": "The created tooltip."
      },
      {
        "key": "onMoveFixPosition",
        "description": "`move` listener for an `Ext.ToolTip` that keeps it usable: when the tooltip has a\n`target`, it is placed to the left or right of that element (whichever side has room,\nor the side nearer to the mouse), and in any case it is pushed back inside the page\nwhen it would overflow the bottom or right edge. `CreateTooltipThumb` wires it\nautomatically; use it as `listeners: {move: ExtjsUtils.TooltipHelper.onMoveFixPosition}`\non tooltips you build yourself.\n@param ttip {Ext.ToolTip} The tooltip being moved.\n@param x {Number} New absolute x position of the tooltip (page coordinates).\n@param y {Number} New absolute y position of the tooltip (page coordinates).",
        "type": "function(ttip, x, y)",
        "qualifiedName": "ExtjsUtils.TooltipHelper.onMoveFixPosition",
        "examples": [
          "new Ext.ToolTip({target: node, html: \"Details\", trackMouse: true,\n    listeners: {move: ExtjsUtils.TooltipHelper.onMoveFixPosition}});"
        ]
      }
    ],
    "ZOOM": [
      {
        "key": "_information",
        "completeAPI": "",
        "description": "Functions to control zoom.",
        "name": "ZOOM · zoom the map"
      },
      {
        "key": "limitZoomLevel",
        "description": "Limits the map to a maximum zoom level: zooms out if the current zoom is above it\nand removes the deeper zoom levels from the map and its base layer. Typically\ncalled from the query `runNow` hook.\n@param zoomLvl {Number} Highest zoom level (integer) the user may reach.",
        "type": "function(zoomLvl)",
        "qualifiedName": "ExtjsUtils.ZOOM.limitZoomLevel",
        "examples": [
          "ExtjsUtils.QUERY.setQueryGlobalProperties({ runNow: function() { ExtjsUtils.ZOOM.limitZoomLevel(17); } }) && QUERY_DESCRIPTION"
        ]
      },
      {
        "key": "zoomToExtent",
        "description": "Zooms the map to the given bounds (shortcut for `app.mapPanel.map.zoomToExtent`). The bounds\nmust be in the map projection (EPSG:900913); use `ExtjsUtils.bboxTransform` or\n`OpenLayers.Bounds.transform` to convert lon/lat first. `maxZoom` keeps a small extent (one\nproperty, one point) from landing on a zoom deeper than useful — e.g. deeper than the zoom\nan offline area was downloaded to. Nothing happens when `bounds` is missing.\n@param bounds {OpenLayers.Bounds|Array.<Number>|Object} Extent to show, in the map projection (any shape `ExtjsUtils.toBounds` accepts).\n@param closest {Boolean} True to pick the zoom level closest to the extent even if it does not fully contain it.\n@param maxZoom {Number} Deepest zoom to end at; omitted ⇒ no limit.",
        "type": "function(bounds, closest, maxZoom) : Number",
        "qualifiedName": "ExtjsUtils.ZOOM.zoomToExtent",
        "examples": [
          "var bounds = new OpenLayers.Bounds(-48, -20, -40, -14).transform(\"EPSG:4326\", \"EPSG:900913\");\nExtjsUtils.ZOOM.zoomToExtent(bounds);",
          "// fit a property, but not past zoom 16\nExtjsUtils.ZOOM.zoomToExtent(ExtjsUtils.LAYER.getLayerExtent(\"CSR:show_car\"), false, 16);"
        ],
        "returnDescription": "The map zoom after the call."
      }
    ]
  },
  "Offline Maps": {
    "_information": {
      "title": "Offline maps",
      "description": "Download named map areas (extent + zoom range) for offline use, with the WMS capabilities and legends they need, export/import them as zip packages and verify they are served from the cache."
    },
    "OFFLINE": [
      {
        "key": "_information",
        "completeAPI": "Runnable example: the offline-areas demo in the repository (examples/offline-areas-demo).",
        "description": "Offline area engine: one IndexedDB record and one Cache Storage bucket per area.\nUsage: ExtjsUtils.OFFLINE",
        "name": "OFFLINE · offline areas"
      },
      {
        "key": "CONCURRENCY",
        "description": "Default number of tile requests `downloadArea` keeps in flight at once (`6`).\nOverride per call with `options.concurrency`.",
        "type": "Number",
        "qualifiedName": "ExtjsUtils.OFFLINE.CONCURRENCY"
      },
      {
        "key": "DB_NAME",
        "description": "Name of the IndexedDB database that keeps the offline area records:\n`'mappia_offline_areas'`. Informational — useful to find the records in the\nbrowser DevTools (Application → IndexedDB).",
        "type": "String",
        "qualifiedName": "ExtjsUtils.OFFLINE.DB_NAME"
      },
      {
        "key": "DB_VERSION",
        "description": "Schema version of the area database (`1`). Bumped by the platform when the\nrecord layout changes; never set it yourself.",
        "type": "Number",
        "qualifiedName": "ExtjsUtils.OFFLINE.DB_VERSION"
      },
      {
        "key": "MAX_TILES_PER_AREA",
        "description": "Default cap on the number of tiles a single area may enumerate (`20000`). The\ntile count grows about 4x per zoom level, so this guardrail stops a pathological\nextent/zoom range before any tile is requested: `downloadArea` rejects with an\n\"Area too large\" error above it. Read it to show the limit in a UI, and pass\n`options.maxTiles` to `downloadArea` for a tighter or looser cap per call.",
        "type": "Number",
        "qualifiedName": "ExtjsUtils.OFFLINE.MAX_TILES_PER_AREA",
        "examples": [
          "var count = ExtjsUtils.OFFLINE.estimateTileCountForLayers(extent, 10, 14);\nif (count > ExtjsUtils.OFFLINE.MAX_TILES_PER_AREA) alert(\"Narrow the extent or the zoom range\");"
        ]
      },
      {
        "key": "STORE_NAME",
        "description": "Name of the object store (inside `DB_NAME`) that holds one record per area,\nkeyed by the area `id`: `'areas'`.",
        "type": "String",
        "qualifiedName": "ExtjsUtils.OFFLINE.STORE_NAME"
      },
      {
        "key": "activateLatestServiceWorker",
        "description": "Activates the newest installed service worker: asks a waiting/installing worker to\nskip waiting and the active one to claim the open pages, then waits (up to ~3 s)\nuntil none is left waiting. A page loaded under an older worker still needs a reload\nbefore its requests reach the new one (`needsReload`). Never rejects.",
        "type": "function() : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.activateLatestServiceWorker",
        "examples": [
          "ExtjsUtils.OFFLINE.activateLatestServiceWorker().then(function(sw) {\n    if (sw.ok && sw.needsReload) location.reload();\n});"
        ],
        "returnDescription": "`getServiceWorkerStatus()` plus `{ok: true, activated}`, or `{ok: false, error}`."
      },
      {
        "key": "checkStorageQuota",
        "description": "Reports the browser's storage quota for this origin (`navigator.storage.estimate`):\nhow much is used, how much the browser grants and the difference. It says nothing\nabout a particular area — compare `available` with `estimateAreaBytes` before a\ndownload. Older browsers without the Storage API report `{unknown: true}`.",
        "type": "function() : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.checkStorageQuota",
        "examples": [
          "ExtjsUtils.OFFLINE.checkStorageQuota().then(function(q) {\n    if (!q.unknown) console.log(Math.round(q.available / 1048576) + \" MB free of \" + Math.round(q.quota / 1048576));\n});"
        ],
        "returnDescription": "`{unknown: false, usage, quota, available}` in bytes, or `{unknown: true}`."
      },
      {
        "key": "collectDefinitionUrls",
        "description": "The definition URLs (not tiles) the given layers need to be redrawn without the\nnetwork: each WMS layer's legend JSON (`/geolegend/?...&isjson=true`) and the\n`getCapabilities.xml` of every remote layer store registered with\n`addRemoteWMSServer` (`storage` stores only; `cors` stores are skipped, they go\nthrough the online proxy). The local WMS GetCapabilities is NOT in the list —\n`downloadDefinitions` prunes and caches it separately. Mostly informational;\n`downloadDefinitions` calls it for you.\n@param layers {Array.<Object>} Targets from `getCacheableLayers`.",
        "type": "function(layers) : Array.<String>",
        "qualifiedName": "ExtjsUtils.OFFLINE.collectDefinitionUrls",
        "examples": [
          "console.log(ExtjsUtils.OFFLINE.collectDefinitionUrls(ExtjsUtils.OFFLINE.getCacheableLayers()));"
        ],
        "returnDescription": "The URLs to cache as-is."
      },
      {
        "key": "createAreaId",
        "description": "Generates a new unique area id (`\"area_<time>_<random>\"`). `downloadArea` and\n`importAreaFromZip` call it when no `id` option is given; call it yourself when\nyou need to know the id before starting a download (e.g. to report progress\nunder it).",
        "type": "function() : String",
        "qualifiedName": "ExtjsUtils.OFFLINE.createAreaId",
        "examples": [
          "var id = ExtjsUtils.OFFLINE.createAreaId();\nExtjsUtils.OFFLINE.downloadArea({id: id, name: \"Fazenda\", extent: extent, zoomMin: 10, zoomMax: 14});"
        ],
        "returnDescription": "A fresh area id."
      },
      {
        "key": "deleteAllAreas",
        "description": "Deletes every offline area (bucket + record) and the definitions buckets those areas\ndownloaded (`definitionsId`), then — once the service worker confirms its index no\nlonger lists them — reloads the map's tiles once (not once per area). The \"clear all\"\nof an areas list: the capabilities caches saved by name (`downloadDefinitions({name})`)\nstay, for `deleteDefinitions`; and unlike `resetAllOfflineData` it keeps the serving\npolicy and leaves the service worker registered.\n@param options {Object} How to delete.\n@param options.reloadTiles {Boolean} `false` to skip the tile reload.",
        "type": "function(options) : Promise.<Number>",
        "qualifiedName": "ExtjsUtils.OFFLINE.deleteAllAreas",
        "examples": [
          "ExtjsUtils.OFFLINE.deleteAllAreas().then(function(count) { console.log(count + \" areas deleted\"); });"
        ],
        "returnDescription": "How many areas were deleted."
      },
      {
        "key": "deleteArea",
        "description": "Deletes an area completely: its Cache Storage bucket (all its tiles) and its\nIndexedDB record, and tells the service worker. The shared definitions bucket is\nnot touched (other areas of the same query may use it; `resetAllOfflineData`\nclears it). Waits for the worker to confirm it rebuilt its index without the\ndeleted bucket, then asks the map to request its tiles again (`reloadMapTiles`),\nso the screen shows the difference at once instead of at the next pan or zoom.\nPass `{reloadTiles: false}` to skip that (e.g. deleting several areas right\nbefore an import replaces them — see `importAreaFromZip`'s `replaceAreaIds`,\nwhich does this itself). Safe to call for an id that no longer exists.\n@param id {String} The area id.\n@param options {Object} `{reloadTiles}` — defaults to `true`.",
        "type": "function(id, options) : Promise.<undefined>",
        "qualifiedName": "ExtjsUtils.OFFLINE.deleteArea",
        "examples": [
          "ExtjsUtils.OFFLINE.deleteArea(areaId).then(function() { console.log(\"deleted\"); });"
        ],
        "returnDescription": "Resolves once both the cache and the record are gone."
      },
      {
        "key": "deleteAreaRecord",
        "description": "Deletes only the IndexedDB record of an area, leaving its tile cache in place.\nNormally you want `deleteArea`, which removes both.\n@param id {String} The area id.",
        "type": "function(id) : Promise.<undefined>",
        "qualifiedName": "ExtjsUtils.OFFLINE.deleteAreaRecord",
        "examples": [
          "ExtjsUtils.OFFLINE.deleteAreaRecord(areaId);"
        ],
        "returnDescription": "Resolves once the record is gone."
      },
      {
        "key": "deleteDefinitions",
        "description": "Deletes a definitions cache (its GetCapabilities and legends). A page still\nloading its catalog from it gets the live one from then on.\n@param id {String} The cache name or id (`listDefinitions`).",
        "type": "function(id) : Promise.<Boolean>",
        "qualifiedName": "ExtjsUtils.OFFLINE.deleteDefinitions",
        "examples": [
          "ExtjsUtils.OFFLINE.deleteDefinitions(\"fazenda-42\");"
        ],
        "returnDescription": "`true` when the cache existed."
      },
      {
        "key": "describeCacheableLayers",
        "description": "One plain description per cacheable map — what a \"which maps go offline\" picker\nshows: `{name, title, visible, kind, maxZoom, suggestedMaxZoom, metersPerCell,\ncanLimitMaxZoom, llbbox}`. `name` is the map name `layerNames` options take\n(`ExtjsUtils.LAYER.getMapName`); `maxZoom` the limit in force (`getMaxZoom`, `null` =\nnone); `suggestedMaxZoom`/`metersPerCell` what `suggestMaxZoom` reads from the\nraster's cell size (`null` when it declares none); `canLimitMaxZoom` whether\n`setMaxZoom` applies (WMS maps, not XYZ/OSM basemaps); `llbbox` the lon/lat\nbounding box from the capabilities (`[left, bottom, right, top]`, or `null`). Plain\ndata, safe to `postMessage`. Call it after `whenLayersReady`.\n@param layerNames {Array.<String>} Only these maps (see `selectCacheableLayers`).",
        "type": "function(layerNames) : Array.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.describeCacheableLayers",
        "examples": [
          "ExtjsUtils.OFFLINE.whenLayersReady().then(function() {\n    ExtjsUtils.OFFLINE.describeCacheableLayers().forEach(function(map) {\n        console.log(map.name + \": max zoom \" + (map.maxZoom === null ? \"none\" : map.maxZoom));\n    });\n});"
        ],
        "returnDescription": "The descriptions, one per map."
      },
      {
        "key": "downloadArea",
        "description": "Downloads (or resumes) one offline area: every tile of the chosen layers over\nthe extent and zoom range, one HTTP request per tile with a bounded concurrency,\ninto the area's own Cache Storage bucket — plus, unless\n`includeDefinitions: false`, the query's definitions (pruned GetCapabilities +\nlegends) into their shared bucket, in parallel (see `downloadDefinitions`). The\narea record is saved as `'downloading'` first and finished as `'ready'`, or\n`'partial'` when aborted or when a tile/definition failed. Resumable: pass the\nsame `id` again and tiles already in the cache are not re-fetched (with\n`refresh: true` they are, each replacing its stored copy — never a second one); with a\ndifferent extent, zoom range or maps the area grows, and its record widens to\ncover both (e.g. one id per imóvel, downloaded again after it is re-drawn). The serving\npolicy (see `setServingPolicy`) is forced to `\"NetworkOnly\"` for the download's\nduration — so every request really goes to the network, never gets answered by\nthe worker from a bucket — and put back to whatever it was, once it settles.\nRejects without any request when the estimated tile count exceeds `maxTiles`\n(\"Area too large\"). The engine works without a query id: `queryId` defaults to the page's\n`?queryid` parameter and is only stored on the record. The service worker serves\nthe cached tiles when the page is offline; use `verifyAreaServed` to check.\n@param options {Object} Area definition and download options.\n@param options.name {String} Display name of the area (defaults to the id).\n@param options.extent {OpenLayers.Bounds|Array.<Number>|Object} The area, in map units: an `OpenLayers.Bounds`, a `[left, bottom, right, top]` array or any `{left, bottom, right, top}` object (e.g. `map.getExtent()`).\n@param options.zoomMin {Number} The first zoom level to download.\n@param options.zoomMax {Number} The last zoom level to download (inclusive).\n@param options.queryId {String} Query id recorded on the area; defaults to the `?queryid` URL parameter (may be empty — not required).\n@param options.id {String} Id of an existing area to resume/refresh; omitted ⇒ `createAreaId()`.\n@param options.layers {Array.<Object>} Targets from `getCacheableLayers` to include; omitted ⇒ `layerNames`, or every cacheable map. Without it the download first waits for the query's layers (`whenLayersReady`).\n@param options.layerNames {Array.<String>} Map names to include (WMS `LAYERS`, or `\"OSM Mapnik\"`) — see `selectCacheableLayers`. Rejects, before any request, when one of them is not on the map.\n@param options.maxTiles {Number} Tile-count cap for this call; defaults to `MAX_TILES_PER_AREA`.\n@param options.concurrency {Number} Parallel tile requests; defaults to `CONCURRENCY`.\n@param options.signal {AbortSignal} Aborts the download; what was fetched stays cached and the area ends `'partial'`.\n@param options.includeDefinitions {Boolean} `false` to download tiles only.\n@param options.refresh {Boolean} `true` to fetch the tiles already stored again, overwriting them (e.g. updated imagery).\n@param options.onProgress {function} Called after each tile with `{done, total, failed, bytes}` (tiles only).",
        "type": "function(options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.downloadArea",
        "examples": [
          "var map = ExtjsUtils.JS.getMap();\nvar controller = new AbortController();\nExtjsUtils.OFFLINE.downloadArea({\n    name: \"Fazenda Santa Clara\",\n    extent: map.getExtent(),\n    zoomMin: map.getZoom(),\n    zoomMax: map.getZoom() + 3,\n    layers: ExtjsUtils.OFFLINE.getCacheableLayers(),\n    signal: controller.signal,\n    onProgress: function(p) { console.log(p.done + \"/\" + p.total + \" tiles, \" + p.failed + \" failed\"); }\n}).then(function(area) {\n    console.log(\"area \" + area.id + \" is \" + area.status);\n}).catch(function(err) {\n    alert(err.message); // e.g. \"Area too large: ~35000 tiles exceeds the 20000 cap...\"\n});\n// controller.abort() cancels; the partial area can be resumed later with {id: area.id}"
        ],
        "returnDescription": "The saved area record (see `listAreas`), with `status` `'ready'` or `'partial'`."
      },
      {
        "key": "downloadAreaAsZip",
        "description": "Same result as `downloadArea`, but as ONE HTTP request instead of one per tile:\nPOSTs the enumerated URL list (`{urls: [...], entries: [{url, map}]}`) to\n`options.endpoint`, a zip-building service that fetches every tile server-side\nand answers with a single .zip (a `manifest.json` with\n`{entries: [{url, path, contentType, status}]}` plus one file per fetched URL),\nthen unpacks it straight into the area's cache (JSZip is lazy-loaded on first\nuse). There is no default endpoint and none built into the platform — it is\ninfrastructure a deployment opts into (a reference implementation,\nmappia-offline-zip-server, lives next to the offline-areas demo). Not resumable\nmid-download: it either completes or is retried from scratch; a URL the service\ncould not fetch is counted as failed. Definitions are never sent to the endpoint:\nthey are fetched (and the capabilities pruned) by the client, in parallel, unless\n`includeDefinitions: false`. Same NetworkOnly-for-the-duration policy switch as\n`downloadArea` (see its doc comment).\n@param options {Object} The same options as `downloadArea` (`name`, `extent`, `zoomMin`, `zoomMax`, `queryId`, `id`, `layers`, `layerNames`, `maxTiles`, `signal`, `includeDefinitions`, `onProgress`) plus the endpoint.\n@param options.endpoint {String} URL of the zip-building service (required; rejects without it).\n@param options.onProgress {function} Called with `{phase, done, total, failed, bytes}`: `phase` is `'requesting'` while the POST is in flight (`done` stays 0), then `'unpacking'` once per entry written to the cache.",
        "type": "function(options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.downloadAreaAsZip",
        "examples": [
          "ExtjsUtils.OFFLINE.downloadAreaAsZip({\n    name: \"Fazenda Santa Clara\",\n    extent: ExtjsUtils.JS.getMap().getExtent(),\n    zoomMin: 11, zoomMax: 14,\n    endpoint: \"https://offline-zip.example.org/zip\",\n    onProgress: function(p) { console.log(p.phase, p.done + \"/\" + p.total); }\n}).then(function(area) { console.log(area.status); });"
        ],
        "returnDescription": "The saved area record (see `listAreas`), with `status` `'ready'` or `'partial'`."
      },
      {
        "key": "downloadDefinitions",
        "description": "Downloads the \"definitions\" (not tiles) the query needs to redraw without the\nnetwork: the local WMS GetCapabilities — pruned to the maps actually on the map\nwith `ExtjsUtils.CAPABILITIES.filterText` before it is cached (the full catalog\nis ~2500 layers; the pruned copy is the handful in use) — plus each layer's\nlegend and every registered remote store's capabilities\n(`collectDefinitionUrls`, cached as-is). They go into their own shared cache\nbucket, separate from any area's tiles, because which maps the filter keeps\ndepends on the whole active query, not on one area: several areas of the same\nquery share one copy. The bucket id is derived from the in-use map names, so\nno `queryid` is required — the engine works without one. `downloadArea` and\n`downloadAreaAsZip` call it in parallel with the tiles unless\n`includeDefinitions: false`; call it directly to refresh definitions alone or to\nget their own progress.\n\nEverything it downloads comes from the server, whatever the serving mode\n(`setServingPolicy`) — never from a cache, its own included — and while offline\n(`navigator.onLine` false, or the Offline preset's `forceOffline`) it rejects at\nonce instead of trying.\n\nGive it a `name` to keep a named capabilities cache the user controls: calling it\nagain with the same name updates that cache; `layerNames` (or `full: true`) picks\nwhich catalog it keeps. A cached catalog is never used on its own: a page loads\nits catalog from it only when it asks — `useDefinitions(name)`, or\n`?definitions=<name>` on the map page URL — so different Mappia instances can use\ndifferent caches, or the live catalog, side by side.\n@param options {Object} Download options.\n@param options.name {String} Name of the cache to create or update (see `listDefinitions`) — any name but `\"live\"`, which means the live catalog; omitted ⇒ an id derived from the map names in use.\n@param options.layerNames {Array.<String>} Maps the cached GetCapabilities keeps; omitted ⇒ the maps on the map now.\n@param options.full {Boolean} `true` to keep the whole GetCapabilities, unfiltered (the full catalog, a few MB).\n@param options.legends {Boolean} `false` to skip the legends.\n@param options.layers {Array.<Object>} Targets from `getCacheableLayers`, scoping the legend/remote-capabilities list only (the GetCapabilities filter always covers the whole map); omitted ⇒ every cacheable layer.\n@param options.signal {AbortSignal} Aborts the pending requests; what was already cached stays.\n@param options.requestTimeoutMs {Number} Longest wait for each request; one that takes longer counts as failed and the rest go on.\n@param options.onProgress {function} Called after each request with `{id, done, total, failed, bytes}`.",
        "type": "function(options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.downloadDefinitions",
        "examples": [
          "ExtjsUtils.OFFLINE.downloadDefinitions({\n    onProgress: function(p) { console.log(\"definitions \" + p.done + \"/\" + p.total); }\n}).then(function(result) {\n    console.log(result.mapNames, result.failed === 0 ? \"complete\" : \"partial\");\n});",
          "// a named cache of this query's maps, then this page loads its catalog from it\nExtjsUtils.OFFLINE.downloadDefinitions({ name: \"fazenda-42\" }).then(function() {\n    return ExtjsUtils.OFFLINE.useDefinitions(\"fazenda-42\");\n});"
        ],
        "returnDescription": "`{id, name, full, cacheName, mapNames, done, failed, total, bytes}` — `id` is the definitions bucket id also stored on the area as `definitionsId`."
      },
      {
        "key": "downloadLayers",
        "description": "`downloadArea` from a list of layers: the area is worked out by `planArea` (maps,\nextent, zoom range and name derived from the layers, each overridable in\n`options`), then downloaded tile by tile — or, with `options.endpoint`, by\n`downloadAreaAsZip`. Every other option goes through as is (`id`, `signal`,\n`onProgress`, `includeDefinitions`, `maxTiles`, `concurrency`, …).\n@param layers {Array.<(String|OpenLayers.Layer)>} Layers by name or the layers themselves (see `planArea`).\n@param options {Object} `planArea` overrides (`extent`, `zoomMin`, `zoomMax`, `name`) plus any `downloadArea` option.\n@param options.endpoint {String} URL of a zip-building service: download with `downloadAreaAsZip` instead.",
        "type": "function(layers, options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.downloadLayers",
        "examples": [
          "// one imóvel, ready for offline editing\nExtjsUtils.OFFLINE.downloadLayers([\"CSR:show_car\", \"CSR:planet_brasil_2024\", \"CSR:cultivo_cafe_cafe\"], {\n    id: \"imovel_\" + imovelId\n}).then(function(area) { console.log(area.name + \" is \" + area.status); });",
          "// the same, but no deeper than zoom 15 and over an extent of your own\nExtjsUtils.OFFLINE.downloadLayers([\"CSR:planet_brasil_2024\"], { extent: [-5179428, -2460070, -5171880, -2455780], zoomMax: 15 });"
        ],
        "returnDescription": "The saved area record (see `listAreas`)."
      },
      {
        "key": "effectiveZoom",
        "description": "The zoom a layer really requests tiles at when the map is at zoom `z`: `z`\nitself, unless the layer has a `maxZoom` limit it honours (`ConfigLayer.maxZoom`\nor `layer.setMaxZoom`), in which case every zoom above the limit collapses onto\nit — the map keeps asking for the max-zoom tile and stretches it. The estimate\nand the enumeration both go through this, which is why a `maxZoom` makes an\noffline area so much smaller.\n@param target {Object} A `{layer, kind}` entry from `getCacheableLayers`.\n@param z {Number} The map zoom level.",
        "type": "function(target, z) : Number",
        "qualifiedName": "ExtjsUtils.OFFLINE.effectiveZoom",
        "examples": [
          "var target = ExtjsUtils.OFFLINE.getCacheableLayers()[0];\nconsole.log(ExtjsUtils.OFFLINE.effectiveZoom(target, 16)); // 13 for a layer limited to maxZoom 13"
        ],
        "returnDescription": "The effective zoom, `<= z`."
      },
      {
        "key": "enumerateTileEntries",
        "description": "`enumerateTileUrls()` with the map each tile belongs to: `[{url, map}]`, where\n`map` is the layer's WMS `LAYERS` name (or its name for XYZ/OSM) — the folder the\ntile lands in when the area is exported as a zip. A URL two layers share (a\nquery listing the same map twice) appears once, under the first layer.\n@param extent {OpenLayers.Bounds|Array.<Number>|Object} The area, in map units: an `OpenLayers.Bounds`, a `[left, bottom, right, top]` array or any `{left, bottom, right, top}` object.\n@param zoomMin {Number} The first zoom level to cover.\n@param zoomMax {Number} The last zoom level to cover (inclusive).\n@param layers {Array.<Object>} Targets from `getCacheableLayers`; omitted ⇒ every cacheable layer on the map.",
        "type": "function(extent, zoomMin, zoomMax, layers) : Array.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.enumerateTileEntries",
        "examples": [
          "var perMap = {};\nExtjsUtils.OFFLINE.enumerateTileEntries(extent, 10, 14).forEach(function(entry) {\n    perMap[entry.map] = (perMap[entry.map] || 0) + 1;\n});\nconsole.log(perMap); // {\"CSR:cultivo_cafe_cafe\": 340, \"mapnik\": 340}"
        ],
        "returnDescription": "Entries `{url: String, map: String}`; `[]` without a map."
      },
      {
        "key": "enumerateTileUrls",
        "description": "The unique tile URLs an area needs: every tile of every given layer, for every\nzoom in range, covering `extent`. Each URL is asked from the live layer object\n(its own `getURL`), so it is byte-identical to what the map requests on its own;\nzooms above a layer's `maxZoom` are enumerated at the limit, exactly like the\nmap behaves. This is the list `estimateAreaBytes` samples and `downloadArea`\nfetches; a large extent/zoom range makes it long, so check\n`estimateTileCountForLayers` first.\n@param extent {OpenLayers.Bounds|Array.<Number>|Object} The area, in map units: an `OpenLayers.Bounds`, a `[left, bottom, right, top]` array or any `{left, bottom, right, top}` object.\n@param zoomMin {Number} The first zoom level to cover.\n@param zoomMax {Number} The last zoom level to cover (inclusive).\n@param layers {Array.<Object>} Targets from `getCacheableLayers`; omitted ⇒ every cacheable layer on the map.",
        "type": "function(extent, zoomMin, zoomMax, layers) : Array.<String>",
        "qualifiedName": "ExtjsUtils.OFFLINE.enumerateTileUrls",
        "examples": [
          "var urls = ExtjsUtils.OFFLINE.enumerateTileUrls(extent, 10, 14, selectedLayers);\nExtjsUtils.OFFLINE.estimateAreaBytes(urls).then(function(estimate) {\n    console.log(urls.length + \" tiles, ~\" + Math.round(estimate.estimatedBytes / 1048576) + \" MB\");\n});"
        ],
        "returnDescription": "The tile URLs, without duplicates; `[]` without a map."
      },
      {
        "key": "estimateArea",
        "description": "Everything to show before downloading an area, in one call, with the same map\nselection and cap `downloadArea` applies: first the rough tile count\n(`estimateTileCountForLayers`, pure arithmetic — an oversized area costs no request)\nchecked against the cap; then, under the cap, the exact tile count, a size estimated\nfrom a sample of real tile requests (`estimateAreaBytes`) and the storage quota\n(`checkStorageQuota`). Waits for the query's layers (`whenLayersReady`). Rejects,\nbefore any request, when one of `layerNames` is not on the map.\n@param options {Object} The area, as `downloadArea` takes it.\n@param options.extent {OpenLayers.Bounds|Array.<Number>|Object} The area, in map units: an `OpenLayers.Bounds`, a `[left, bottom, right, top]` array or any `{left, bottom, right, top}` object.\n@param options.zoomMin {Number} The first zoom level.\n@param options.zoomMax {Number} The last zoom level (inclusive).\n@param options.layerNames {Array.<String>} Maps to include (see `selectCacheableLayers`); omitted ⇒ every cacheable map.\n@param options.layers {Array.<Object>} Targets from `getCacheableLayers`, instead of `layerNames`.\n@param options.maxTiles {Number} The cap; defaults to `MAX_TILES_PER_AREA`.",
        "type": "function(options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.estimateArea",
        "examples": [
          "ExtjsUtils.OFFLINE.estimateArea({ extent: map.getExtent(), zoomMin: 12, zoomMax: 16 }).then(function(estimate) {\n    if (estimate.overCap) alert(\"~\" + estimate.roughTileCount + \" tiles: narrow the area\");\n    else console.log(estimate.tileCount + \" tiles, ~\" + Math.round(estimate.estimatedBytes / 1048576) + \" MB\");\n});"
        ],
        "returnDescription": "`{layerNames, roughTileCount, tileCountWithoutMaxZoom, maxTiles, overCap}`, plus — when not over the cap — `{tileCount, sampledCount, estimatedBytes, quota}`. `tileCountWithoutMaxZoom` is what the same area would cost if no map had a `maxZoom` limit: the difference to `roughTileCount` is what the limits save."
      },
      {
        "key": "estimateAreaBytes",
        "description": "Estimates an area's download size in bytes by really fetching a small sample of\nits tile URLs, measuring them and extrapolating by the total count — tile weight\nvaries by imagery, zoom and source, so nothing short of asking the network is an\nestimate. Costs `sampleSize` requests. Distinct from `checkStorageQuota`, which\nreports the browser's free storage, not anything about this area; label the two\nseparately in a UI.\n@param urls {Array.<String>} The tile URLs of the area, from `enumerateTileUrls`.\n@param sampleSize {Number} How many of the URLs (from the start of the list) to fetch.",
        "type": "function(urls, sampleSize) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.estimateAreaBytes",
        "examples": [
          "var urls = ExtjsUtils.OFFLINE.enumerateTileUrls(extent, 10, 14, selectedLayers);\nPromise.all([ExtjsUtils.OFFLINE.estimateAreaBytes(urls, 8), ExtjsUtils.OFFLINE.checkStorageQuota()])\n    .then(function(results) {\n        var needed = results[0].estimatedBytes, quota = results[1];\n        if (!quota.unknown && needed > quota.available) alert(\"Not enough storage for this area\");\n    });"
        ],
        "returnDescription": "`{tileCount, sampledCount, averageBytes, estimatedBytes}` — `sampledCount` is the number of sample fetches that succeeded (0 ⇒ estimate is 0)."
      },
      {
        "key": "estimateTileCount",
        "description": "Number of tile-grid cells covering `extent` across the zoom range for ONE layer\nwith NO zoom limit — pure arithmetic, no layer involved. Use it for the raw\ngrid size (e.g. to show how much a `maxZoom` saves); `estimateTileCountForLayers`\nis the one to use for the real cost, since it knows about each layer's limit.\n@param extent {OpenLayers.Bounds|Array.<Number>|Object} The area, in map units: an `OpenLayers.Bounds`, a `[left, bottom, right, top]` array or any `{left, bottom, right, top}` object.\n@param zoomMin {Number} The first zoom level to cover.\n@param zoomMax {Number} The last zoom level to cover (inclusive).",
        "type": "function(extent, zoomMin, zoomMax) : Number",
        "qualifiedName": "ExtjsUtils.OFFLINE.estimateTileCount",
        "examples": [
          "var extent = ExtjsUtils.JS.getMap().getExtent();\nconsole.log(ExtjsUtils.OFFLINE.estimateTileCount(extent, 10, 14) + \" cells per unlimited layer\");"
        ],
        "returnDescription": "The cell count, `0` without a map."
      },
      {
        "key": "estimateTileCountForLayers",
        "description": "Upper bound on the tile requests an area would need for these layers over the\nzoom range, honouring each layer's `maxZoom` (zooms above it count once, at the\nlimit). Still pure arithmetic — no URL is built — so run it first, before\nenumerating or fetching anything, to reject an oversized area cheaply: it is the\nsame number `downloadArea` compares against `MAX_TILES_PER_AREA`.\n@param extent {OpenLayers.Bounds|Array.<Number>|Object} The area, in map units: an `OpenLayers.Bounds`, a `[left, bottom, right, top]` array or any `{left, bottom, right, top}` object.\n@param zoomMin {Number} The first zoom level to cover.\n@param zoomMax {Number} The last zoom level to cover (inclusive).\n@param layers {Array.<Object>} Targets from `getCacheableLayers`; omitted ⇒ every cacheable layer on the map.",
        "type": "function(extent, zoomMin, zoomMax, layers) : Number",
        "qualifiedName": "ExtjsUtils.OFFLINE.estimateTileCountForLayers",
        "examples": [
          "var extent = ExtjsUtils.JS.getMap().getExtent();\nvar rough = ExtjsUtils.OFFLINE.estimateTileCountForLayers(extent, 10, 14, selectedLayers);\nif (rough > ExtjsUtils.OFFLINE.MAX_TILES_PER_AREA) {\n    alert(\"Too large: ~\" + rough + \" tiles. Narrow the extent or the zoom range.\");\n}"
        ],
        "returnDescription": "The estimated tile count, `0` without a map."
      },
      {
        "key": "exportAreaAsZip",
        "description": "Packs an existing area — every tile currently in its cache — into one .zip Blob\nthat is self-describing: a `manifest.json` (entries `{url, path, map, contentType,\nstatus}` plus an `area` block with the record's id, name, extent, zoom range,\nlayer names and query id) and one file per tile, laid out one folder per map\n(`<map>/<host>/<path>`, e.g. `mapnik/tile.openstreetmap.org/16/23456/37891.png`).\n`importAreaFromZip`/`importAreaFromUrl` rebuild the area from it on another\ndevice without re-downloading a single tile — host the file anywhere. Tiles are\nstored uncompressed (they are PNG/JPEG already) and the zip is assembled in\nmemory: fine for the thousands of tiles an area holds, not for hundreds of MB.\nPair it with `exportAreaFilename` and `saveBlobAsFile` to hand the pack to the user.\n@param id {String} The area id.\n@param options {Object} Export options.\n@param options.onProgress {function} Called per tile with `{phase: 'packing', done, total, failed, bytes}`.",
        "type": "function(id, options) : Promise.<Blob>",
        "qualifiedName": "ExtjsUtils.OFFLINE.exportAreaAsZip",
        "examples": [
          "ExtjsUtils.OFFLINE.getArea(areaId).then(function(area) {\n    return ExtjsUtils.OFFLINE.exportAreaAsZip(areaId).then(function(blob) {\n        ExtjsUtils.OFFLINE.saveBlobAsFile(blob, ExtjsUtils.OFFLINE.exportAreaFilename(area));\n    });\n});"
        ],
        "returnDescription": "The zip file; rejects when no area has that id."
      },
      {
        "key": "exportAreaFilename",
        "description": "The file name a pack from `exportAreaAsZip` should be saved under:\n`mappia-offline-area-<slug>.zip`, where the slug is the area name (falling back\nto its id) lower-cased, accents stripped and non-alphanumerics replaced by `-`.\n@param area {Object} The area record (only `name`/`id` are read).",
        "type": "function(area) : String",
        "qualifiedName": "ExtjsUtils.OFFLINE.exportAreaFilename",
        "examples": [
          "ExtjsUtils.OFFLINE.exportAreaFilename({name: \"Fazenda São João\"}); // \"mappia-offline-area-fazenda-sao-joao.zip\""
        ],
        "returnDescription": "The file name, e.g. `\"mappia-offline-area-fazenda-santa-clara.zip\"`."
      },
      {
        "key": "exportDefinitionsAsZip",
        "description": "Packs a definitions cache (`listDefinitions`) — its GetCapabilities and legends — into\none .zip Blob: a `manifest.json` (`format: 'mappia-offline-definitions'`, the cache's\nmetadata as `definitions`, and `entries` `{url, path, contentType}`) plus one file per\nentry. `importDefinitionsFromZip` saves it back on any device, where `?definitions=<id>`\nor `useDefinitions` then loads the map's catalog from it without the server.\n@param id {String} The cache name or id.",
        "type": "function(id) : Promise.<Blob>",
        "qualifiedName": "ExtjsUtils.OFFLINE.exportDefinitionsAsZip",
        "examples": [
          "ExtjsUtils.OFFLINE.exportDefinitionsAsZip(\"fazenda-42\").then(function(blob) {\n    ExtjsUtils.OFFLINE.saveBlobAsFile(blob, \"mappia-capabilities-fazenda-42.zip\");\n});"
        ],
        "returnDescription": "The zip file; rejects when no cache has that id."
      },
      {
        "key": "getArea",
        "description": "Reads one area record by id (see `listAreas` for the record shape).\n@param id {String} The area id.",
        "type": "function(id) : Promise.<(Object|null)>",
        "qualifiedName": "ExtjsUtils.OFFLINE.getArea",
        "examples": [
          "ExtjsUtils.OFFLINE.getArea(areaId).then(function(area) {\n    if (!area) throw new Error(\"area not found: \" + areaId);\n    console.log(area.status);\n});"
        ],
        "returnDescription": "The record, or `null` when no area has that id."
      },
      {
        "key": "getCacheableLayers",
        "description": "Lists the layers currently on the map whose tiles can be cached for offline use,\nas `{layer, kind}` \"targets\" — the objects every other function here accepts as\n`layers`. Two kinds: `'storage'` (a CustomWMS layer served from a hosted layer\nstore — `addRemoteWMSServer` with `storage` — recursing into a Composed layer's\ninner layers) and `'grid'` (any other tiled `OpenLayers.Layer.Grid`: XYZ/OSM\nbasemaps and plain named WMS layers such as `CSR:cultivo_cafe_cafe`). Excluded:\ncalculate layers with an operation but no store (their requests depend on the\nviewport, not on a tile grid) and Google/Bing basemaps (their terms forbid\ncaching). Filter the result to let the user pick which maps to include; the\nWMS name of a target is `target.layer.params.LAYERS` (`target.layer.name` for\nXYZ/OSM). Wait for `whenLayersReady` before calling it, or a\n`{name: \"CSR:...\"}` layer may not be on the map yet; `selectCacheableLayers`\npicks maps by name.",
        "type": "function() : Array.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.getCacheableLayers",
        "examples": [
          "var targets = ExtjsUtils.OFFLINE.getCacheableLayers();\nvar selected = targets.filter(function(t) {\n    var key = (t.layer.params && t.layer.params.LAYERS) || t.layer.name;\n    return [\"CSR:cultivo_cafe_cafe\", \"mapnik\"].indexOf(key) !== -1;\n});"
        ],
        "returnDescription": "Targets `{layer: OpenLayers.Layer, kind: 'storage'|'grid'}`, or `[]` without a map."
      },
      {
        "key": "getDefinitionsInUse",
        "description": "The definitions cache this page loads its catalog from (`useDefinitions` or\n`?definitions=<name>`), `\"\"` for the empty catalog it started with (`?definitions=`),\nor `null` for the live catalog.",
        "type": "function() : String|null",
        "qualifiedName": "ExtjsUtils.OFFLINE.getDefinitionsInUse",
        "examples": [
          "console.log(ExtjsUtils.OFFLINE.getDefinitionsInUse() || \"live catalog\");"
        ],
        "returnDescription": "The cache id, `\"\"`, or `null`."
      },
      {
        "key": "getMaxZoom",
        "description": "The zoom limit a layer currently honours (see `effectiveZoom`), or `null` when it\nhas none. Shows the \"real max zoom\" in force for a layer — set in the query with\n`ConfigLayer.maxZoom` or at runtime with `layer.setMaxZoom(n)` on CustomWMS layers.\n@param target {Object} A `{layer, kind}` entry from `getCacheableLayers`.",
        "type": "function(target) : Number|null",
        "qualifiedName": "ExtjsUtils.OFFLINE.getMaxZoom",
        "examples": [
          "ExtjsUtils.OFFLINE.getCacheableLayers().forEach(function(t) {\n    console.log(t.layer.name, ExtjsUtils.OFFLINE.getMaxZoom(t));\n});"
        ],
        "returnDescription": "The max zoom, or `null`."
      },
      {
        "key": "getOfflineReadiness",
        "description": "Whether this page is ready to work offline, as four checks, each `{ok, ...}`:\n- `worker`: the current service worker controls the page (`reason` when not:\n  `'not-controlled'`, `'outdated'` — reload the page, or `'disabled'`);\n- `tiles`: an area that is switched on and fully downloaded (`status: 'ready'`)\n  covers `options.extent` (any such area without one) — `areaIds`;\n- `catalog`: the map's catalog comes from a saved capabilities cache that exists\n  (`inUse`, from `getDefinitionsInUse`; `saved`, the cache ids of `listDefinitions`);\n- `serving`: the serving policy answers from the downloads (any mode but\n  `NetworkOnly`) — `mode`, `forceOffline`.\n`ready` is `true` when all four are; `missing` lists the keys that are not. Built from\n`getServiceWorkerStatus`, `listAreas`, `listDefinitions`, `getDefinitionsInUse` and\n`getServingPolicy`. Plain data, safe to `postMessage`.\n@param options {Object} What to check.\n@param options.extent {OpenLayers.Bounds|Array.<Number>|Object} The area that must be covered, in map units.",
        "type": "function(options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.getOfflineReadiness",
        "examples": [
          "ExtjsUtils.OFFLINE.getOfflineReadiness({ extent: ExtjsUtils.LAYER.getLayerExtent(\"CSR:show_car\") }).then(function(r) {\n    console.log(r.ready ? \"ready offline\" : \"missing: \" + r.missing.join(\", \"));\n});"
        ],
        "returnDescription": "`{ready, missing, worker, tiles, catalog, serving}`."
      },
      {
        "key": "getServiceWorkerStatus",
        "description": "The state of the page's service worker, for a status panel. `registered`,\n`controlled` (a worker answers this page's requests), `controllerScriptURL`,\n`controllerIsCurrent` (the controller is the active worker), the registration slots\n`active`/`waiting`/`installing` (their `state`, or `null`) with `activeMeta`/\n`waitingMeta`/`installingMeta` (`{state, scriptURL}`), `hasWaiting`, `needsReload`\n(a newer worker waits, or an older one still controls the page — reload the page or\niframe to run the newest), and from the worker's own answer: `build` (its build\nlabel, a hash of its code), `offlineBuckets`, `servingPolicy`, `recentDebugEvents`,\n`workerScriptURL`. Also carries the page's registration outcome\n(`window.MappiaServiceWorkerSetup`: `enabled`, `localhost`, `error`). Plain data, safe\nto `postMessage`. Never rejects.",
        "type": "function() : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.getServiceWorkerStatus",
        "examples": [
          "ExtjsUtils.OFFLINE.getServiceWorkerStatus().then(function(sw) {\n    console.log(sw.controlled ? \"worker \" + sw.build : \"no worker controls this page\");\n    if (sw.needsReload) console.log(\"reload to run the newest worker\");\n});"
        ],
        "returnDescription": "The status."
      },
      {
        "key": "getServingPolicy",
        "description": "How the service worker answers map requests that hit (or miss) an offline\nbucket: `{mode}` with one of\n`\"CacheFirst\"` (default: bucket first, network on miss),\n`\"NetworkFirst\"` (live first, bucket when the network fails),\n`\"CacheOnly\"` (bucket / soft crop only — never the network),\n`\"NetworkOnly\"` (live only — never the bucket).\nLegacy kebab-case values (`cache-first`, …) are accepted and normalized.\n`forceOffline: true` makes the worker treat the network as down no matter\nwhat `navigator.onLine` says — every mode then behaves as it would with no\nnetwork at all (an \"Offline\" preset is `{mode: \"CacheOnly\", forceOffline: true}`).\nStored in Cache Storage (`OfflineCacheKey.SETTINGS_CACHE_NAME`);\n`resetAllOfflineData` clears it.",
        "type": "function() : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.getServingPolicy",
        "examples": [
          "ExtjsUtils.OFFLINE.getServingPolicy().then(function(policy) { console.log(policy.mode); });"
        ],
        "returnDescription": "`{mode, forceOffline, updatedAt}` — the default (`forceOffline: false`, `updatedAt: null`) when nothing was set yet."
      },
      {
        "key": "importAreaFromUrl",
        "description": "`importAreaFromZip` for a pack hosted at a URL (a GitHub release asset, a raw\nfile in a repository, any static host): one GET, then the unpack — zero tile\nrequests. The host must allow cross-origin reads (CORS) when it is not the\nplatform's own origin; raw.githubusercontent.com and GitHub release assets do.\nThe pack URL is recorded on the area as `importedFrom`.\n@param url {String} The pack URL.\n@param options {Object} The same options as `importAreaFromZip` (`id`, `name`, `extent`, `zoomMin`, `zoomMax`, `layerNames`, `queryId`, `replaceAreaIds`, `signal`, `onProgress`).\n@param options.onProgress {function} Called with `{phase, done, total, failed, bytes}`: `phase` is `'requesting'` while the GET is in flight, then `'unpacking'` per entry.",
        "type": "function(url, options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.importAreaFromUrl",
        "examples": [
          "// step through properties one at a time, never keeping more than one area's tiles on disk\nExtjsUtils.OFFLINE.importAreaFromUrl(\"https://github.com/org/packs/releases/download/v1/fazenda-42.zip\", {\n    id: \"current\",\n    replaceAreaIds: [\"current\"],\n    onProgress: function(p) { console.log(p.phase, p.done + \"/\" + p.total); }\n}).then(function(area) {\n    ExtjsUtils.JS.getMap().zoomToExtent(OpenLayers.Bounds.fromArray(area.extent));\n});"
        ],
        "returnDescription": "The saved area record (see `listAreas`)."
      },
      {
        "key": "importAreaFromZip",
        "description": "Rebuilds an offline area from a pack produced by `exportAreaAsZip` (or any zip\nfollowing the manifest contract) — \"load the tile cache from this file instead\nof downloading every tile\". The area record comes from the pack's `manifest.area`\nand can be overridden by options; a pack without an `area` block still imports\nwith whatever the options give. Re-importing a pack with the same `id` refreshes\nthat area in place (entries are overwritten, never duplicated). Definitions are\nnot part of a pack; run `downloadDefinitions` separately when the device needs\nthem. See `importAreaFromUrl` for packs hosted on a server.\n@param zipBlob {Blob} The pack, e.g. from an `<input type=\"file\">` or a `fetch().blob()`.\n@param options {Object} Import options.\n@param options.id {String} Area id to create/refresh; defaults to the pack's, else a new id.\n@param options.name {String} Area name; defaults to the existing/packed name, else the id.\n@param options.extent {Array.<Number>} `[left, bottom, right, top]` in map units; defaults to the packed extent.\n@param options.zoomMin {Number} Defaults to the packed value.\n@param options.zoomMax {Number} Defaults to the packed value.\n@param options.layerNames {Array.<String>} Map names the pack covers; defaults to the packed list.\n@param options.queryId {String} Defaults to the packed value.\n@param options.replaceAreaIds {Array.<String>} Ids of other areas to delete (cache + record) BEFORE unpacking — the \"one property open at a time\" tool: `{id: 'current', replaceAreaIds: ['current']}` never keeps more than one area's tiles on disk, freeing space before the new pack lands.\n@param options.signal {AbortSignal} Aborts the unpacking; the area ends `'partial'`.\n@param options.onProgress {function} Called per entry with `{phase: 'unpacking', done, total, failed, bytes}`.\n@param options.sourceUrl {String} Recorded as `importedFrom` (set automatically by `importAreaFromUrl`).",
        "type": "function(zipBlob, options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.importAreaFromZip",
        "examples": [
          "// <input type=\"file\" id=\"pack\"> in the host page, forwarded as a Blob\nExtjsUtils.OFFLINE.importAreaFromZip(fileBlob, {\n    name: \"Fazenda Santa Clara\",\n    onProgress: function(p) { console.log(p.done + \"/\" + p.total); }\n}).then(function(area) { console.log(\"imported\", area.id, area.tileCount + \" tiles\"); });"
        ],
        "returnDescription": "The saved area record (see `listAreas`)."
      },
      {
        "key": "importDefinitionsFromZip",
        "description": "Saves a capabilities pack from `exportDefinitionsAsZip` as a definitions cache, under\nits packed id or `options.name` (an existing cache of that id is replaced). Then\n`?definitions=<id>` or `useDefinitions(id)` loads the map's catalog from it.\n@param zipBlob {Blob} The pack, e.g. from an `<input type=\"file\">`.\n@param options {Object} Import options.\n@param options.name {String} The cache name; defaults to the packed one.",
        "type": "function(zipBlob, options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.importDefinitionsFromZip",
        "examples": [
          "ExtjsUtils.OFFLINE.importDefinitionsFromZip(fileBlob).then(function(definitions) {\n    return ExtjsUtils.OFFLINE.useDefinitions(definitions.id);\n});"
        ],
        "returnDescription": "The cache's metadata, as `listDefinitions` lists it."
      },
      {
        "key": "listAreas",
        "description": "Lists every offline area saved on this browser, as area records. An area record\nis a plain object with `id`, `name`, `extent` (`[left, bottom, right, top]` in map\nunits), `zoomMin`, `zoomMax`, `layerNames` (the WMS map names / basemap names it\ncovers), `queryId`, `status` (`'downloading'`, `'ready'` or `'partial'` — partial\nmeans aborted or some tile/definition failed), `tileCount`, `failedCount`,\n`bytes`, `createdAt`, `updatedAt` and, when definitions were downloaded,\n`definitionsId`, `definitionsBytes`, `definitionsFailedCount`; imported packs also\ncarry `importedFrom`. This is what an \"offline areas\" list in a host page shows.",
        "type": "function() : Promise.<Array.<Object>>",
        "qualifiedName": "ExtjsUtils.OFFLINE.listAreas",
        "examples": [
          "ExtjsUtils.OFFLINE.listAreas().then(function(areas) {\n    areas.forEach(function(area) {\n        console.log(area.name, area.status, area.tileCount + \" tiles\", Math.round(area.bytes / 1048576) + \" MB\");\n    });\n});"
        ],
        "returnDescription": "The area records, in store order."
      },
      {
        "key": "listDefinitions",
        "description": "The definitions caches kept in this browser (see `downloadDefinitions`), each as\nits metadata: `{id, name, full, mapNames, capabilitiesUrl, done, failed, bytes,\nupdatedAt}` (`full`: the whole GetCapabilities, else only `mapNames`). Pass the\n`id` to `useDefinitions`, `deleteDefinitions`, or `downloadDefinitions({name})` to\nupdate it.",
        "type": "function() : Promise.<Array.<Object>>",
        "qualifiedName": "ExtjsUtils.OFFLINE.listDefinitions",
        "examples": [
          "ExtjsUtils.OFFLINE.listDefinitions().then(function(list) {\n    list.forEach(function(d) { console.log(d.name, d.full ? \"full catalog\" : d.mapNames.join(\", \")); });\n});"
        ],
        "returnDescription": "The caches, by name."
      },
      {
        "key": "onMapCacheEvent",
        "description": "Subscribes to the service worker's live feed of how it answered the map's tile\nrequests: one event per cache hit, coarser-zoom crop or miss (`{build, mode, layers,\nrequestZoom, cachedZoom, ...}`, sent by ServiceWorker.js), completed with what only\nthe page knows — `mapZoom`, the map's current zoom, and `layerMaxZoom`, the layer's\nmax-zoom limit when it has one (a layer limited to 15 asks for zoom-15 tiles while\nthe map sits at 17). For a debug panel or a log. Nothing arrives while no worker\ncontrols the page.\n@param callback {function} Called with each event.",
        "type": "function(callback) : function",
        "qualifiedName": "ExtjsUtils.OFFLINE.onMapCacheEvent",
        "examples": [
          "var stop = ExtjsUtils.OFFLINE.onMapCacheEvent(function(event) {\n    console.log(event.layers + \" z\" + event.requestZoom + \" served from z\" + event.cachedZoom);\n});"
        ],
        "returnDescription": "Call it to unsubscribe."
      },
      {
        "key": "openDb",
        "description": "Opens (and on first use creates) the IndexedDB database of area records,\ncaching the connection for the page. The record functions below call it for\nyou; use it directly only for custom queries on the store.",
        "type": "function() : Promise.<IDBDatabase>",
        "qualifiedName": "ExtjsUtils.OFFLINE.openDb",
        "examples": [
          "ExtjsUtils.OFFLINE.openDb().then(function(db) { console.log(db.objectStoreNames.contains(\"areas\")); });"
        ],
        "returnDescription": "The open database."
      },
      {
        "key": "planArea",
        "description": "Works an offline area out from a list of layers — name what goes offline, and the\nrest is derived:\n- the maps to download: the cacheable layers in the list (WMS, XYZ/OSM); when the\n  list has none, every cacheable map on the map;\n- `extent`: the bounds of the features of the vector layers in the list (the\n  imóvel drawn by `CSR:show_car`, say); without any, the current map view;\n- `zoomMin`: 0, so the area also shows zoomed out (the levels above the extent\n  cost a few tiles each);\n- `zoomMax`: the deepest `maxZoom` among those maps (their detail ends there —\n  `getMaxZoom`), or 2 levels below the zoom at which the extent fits the map when\n  none has a limit;\n- `name`: the title of the first vector layer, else the map names.\nAny of them can be given in `options` instead (a missing, `null` or `\"\"` value is\nderived), e.g. a smaller `zoomMax` to limit the zoom or an `extent` of your own.\nWaits for the query's layers. Rejects when a listed layer is not on the map, or is\nneither cacheable nor a vector layer. The result is plain data (postMessage-safe)\nand is what `downloadArea`/`estimateArea` take — `downloadLayers` does both steps.\n@param layers {Array.<(String|OpenLayers.Layer)>} Layers by name (`LAYER.getMapName` or `layer.name`), or the layers themselves.\n@param options {Object} Values to use instead of the derived ones.\n@param options.extent {OpenLayers.Bounds|Array.<Number>|Object} The area, in map units.\n@param options.zoomMin {Number} The first zoom level.\n@param options.zoomMax {Number} The last zoom level (inclusive) — the zoom limit.\n@param options.name {String} Display name of the area.",
        "type": "function(layers, options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.planArea",
        "examples": [
          "// the imóvel's maps, its extent, down to the maps' own max zoom\nExtjsUtils.OFFLINE.planArea([\"CSR:show_car\", \"CSR:planet_brasil_2024\", \"CSR:cultivo_cafe_cafe\"]).then(function(plan) {\n    console.log(plan.name + \": zoom \" + plan.zoomMin + \"-\" + plan.zoomMax + \", \" + plan.layerNames.join(\", \"));\n    return ExtjsUtils.OFFLINE.estimateArea(plan);\n});"
        ],
        "returnDescription": "`{layerNames, extent: [left, bottom, right, top], zoomMin, zoomMax, name}`."
      },
      {
        "key": "prepareOffline",
        "description": "Gets this page ready to work offline over an area in one call — the steps\n`getOfflineReadiness` checks, in order: downloads the tiles of the area worked out from\n`layers` (`downloadLayers`, `planArea` overrides in `options`), saves the map's catalog\nunder a name (`downloadDefinitions`: the maps in use, so the map can be rebuilt from it),\nswitches this page to it (`useDefinitions`) and, when the serving policy is `NetworkOnly`,\nsets `CacheFirst` so the downloads are used. Needs the network; rejects at once offline.\nTo open the map with the catalog next time, use `?definitions=<catalogName>`.\n@param layers {Array.<(String|OpenLayers.Layer)>} Layers by name or the layers themselves (see `planArea`).\n@param options {Object} `downloadLayers` options (`id`, `name`, `extent`, `zoomMin`, `zoomMax`, …) plus:\n@param options.catalogName {String} The catalog's cache name; defaults to the area id.\n@param options.onProgress {function} Called with `{phase: 'tiles'|'catalog'|'switching', ...}` (`done`, `total`, `failed`, `bytes` while downloading).",
        "type": "function(layers, options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.prepareOffline",
        "examples": [
          "// the imóvel and its maps, ready for the field\nExtjsUtils.OFFLINE.prepareOffline([\"CSR:show_car\", \"CSR:planet_brasil_2024\", \"CSR:cultivo_cafe_cafe\"], {\n    id: \"imovel_42\",\n    onProgress: function(p) { console.log(p.phase, p.done, p.total); }\n}).then(function(result) {\n    console.log(result.readiness.ready ? \"ready offline\" : \"missing: \" + result.readiness.missing.join(\", \"));\n});"
        ],
        "returnDescription": "`{area, catalog, readiness}`: the area record, the `downloadDefinitions` result and `getOfflineReadiness({extent})` at the end."
      },
      {
        "key": "reloadMapTiles",
        "description": "Asks the tiled layers on the map for their tiles again, so what the screen shows\nfollows the current serving policy and bucket contents right away instead of at\nthe next pan or zoom. Every tile URL gets a fresh `_dc` value, so the browser\ncannot answer from its own memory/HTTP cache and the service worker sees each\nrequest; `OfflineCacheKey.normalize` drops `_dc`, so the offline buckets still\nmatch. WMS layers (and the maps inside a Composed layer) carry it as a request\nparameter, XYZ/OSM basemaps on their URL template. Hidden layers only get the new\nURLs (they request them when shown). `setServingPolicy` calls this for you.\n@param options {Object} `{layers}` — the `OpenLayers.Layer`s, or `getCacheableLayers()` targets, to reload; default: every layer on the map.",
        "type": "function(options) : Number",
        "qualifiedName": "ExtjsUtils.OFFLINE.reloadMapTiles",
        "examples": [
          "ExtjsUtils.OFFLINE.reloadMapTiles();",
          "// only the downloaded-area layers\nExtjsUtils.OFFLINE.reloadMapTiles({ layers: ExtjsUtils.OFFLINE.getCacheableLayers() });"
        ],
        "returnDescription": "How many layers were asked to reload."
      },
      {
        "key": "resetAllOfflineData",
        "description": "Full reset (\"Reset offline data\"): deletes every offline area (cache + record),\nevery shared definitions bucket and — when a service worker controls the page —\nthe app-shell/dynamic caches and the worker registration itself (the worker's\nself-destruct path, waited for up to 3 s). The page keeps working online; reload\nit to register the worker again. Use it for a \"clear all offline data\" button.",
        "type": "function() : Promise.<undefined>",
        "qualifiedName": "ExtjsUtils.OFFLINE.resetAllOfflineData",
        "examples": [
          "ExtjsUtils.OFFLINE.resetAllOfflineData().then(function() { location.reload(); });"
        ],
        "returnDescription": "Resolves once everything is cleared."
      },
      {
        "key": "sanitizeAreaId",
        "description": "Turns a user/host-provided cache id into a safe Cache Storage / IndexedDB key\nsegment (`[a-z0-9_-]`, max 80). Empty input ⇒ `null` (caller should use\n`createAreaId`). The Cache Storage bucket is always\n`OfflineCacheKey.areaCacheName(id)` = `mappia_offline_area_<id>`; the same `id`\nis the IndexedDB record key.\n@param raw {String} Proposed id (e.g. `\"fazenda-santa-clara\"`).",
        "type": "function(raw) : String|null",
        "qualifiedName": "ExtjsUtils.OFFLINE.sanitizeAreaId",
        "examples": [
          "ExtjsUtils.OFFLINE.sanitizeAreaId(\"Fazenda Santa Clara!\"); // \"fazenda_santa_clara\""
        ],
        "returnDescription": "Sanitized id, or `null` when nothing usable remains."
      },
      {
        "key": "saveAreaRecord",
        "description": "Inserts or replaces an area record (keyed by `area.id`), stamping `updatedAt`.\nThe download/import functions save the record themselves; call it to persist\nyour own edits to a record, e.g. a renamed area.\n@param area {Object} The area record to store (see `listAreas` for its shape); mutated to set `updatedAt`.",
        "type": "function(area) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.saveAreaRecord",
        "examples": [
          "ExtjsUtils.OFFLINE.getArea(areaId).then(function(area) {\n    area.name = \"Talhão norte\";\n    return ExtjsUtils.OFFLINE.saveAreaRecord(area);\n});"
        ],
        "returnDescription": "The same record, once written."
      },
      {
        "key": "saveBlobAsFile",
        "description": "Hands a Blob to the browser as a file download (a programmatic `<a download>`\nclick). Works from inside the embedding iframe as long as it is not sandboxed\nwithout `allow-downloads`; the Blob never has to cross postMessage.\n@param blob {Blob} The file content, e.g. the zip from `exportAreaAsZip`.\n@param filename {String} The suggested file name.",
        "type": "function(blob, filename)",
        "qualifiedName": "ExtjsUtils.OFFLINE.saveBlobAsFile",
        "examples": [
          "ExtjsUtils.OFFLINE.saveBlobAsFile(blob, \"mappia-offline-area-fazenda.zip\");"
        ]
      },
      {
        "key": "selectCacheableLayers",
        "description": "`getCacheableLayers`, one target per map name and optionally only the named maps —\nthe selection `downloadArea`'s `layerNames` makes. The map name is the WMS `LAYERS`\n(e.g. `\"CSR:planet_brasil_2024\"`), or `layer.name` for XYZ/OSM (`\"OSM Mapnik\"`);\nit is what an area's `layerNames` record. When a map is on the map twice (a query\nadding an OSM basemap the platform already shows), the visible copy is kept. Names\nnot on the map are left out — compare the lengths to detect them.\n@param layerNames {Array.<String>} Map names to keep, in this order; omitted ⇒ every cacheable map.",
        "type": "function(layerNames) : Array.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.selectCacheableLayers",
        "examples": [
          "var targets = ExtjsUtils.OFFLINE.selectCacheableLayers([\"CSR:planet_brasil_2024\", \"CSR:cultivo_cafe_cafe\"]);"
        ],
        "returnDescription": "Targets `{layer, kind}` as `getCacheableLayers` returns them."
      },
      {
        "key": "setAreaEnabled",
        "description": "Switches one downloaded area on or off for serving, keeping its tiles: off, the\nworker leaves that bucket out of its index, so nothing in it is answered from the\ncache (nor used for the coarser-zoom crop) until it is switched on again — the\nway to keep several areas downloaded (e.g. the same extent at zoom 14-16 and at\n11-13, as two areas) and choose which one is live to test. Stored on the record\n(`area.enabled`, `undefined` = on) and in the bucket's own metadata entry, where\nthe worker reads it. Waits for the worker to confirm it rebuilt its index under\nthe new state, then asks the map to request its tiles again (`reloadMapTiles`),\nso the screen shows the difference at once instead of at the next pan or zoom.\nPass `{reloadTiles: false}` to only store the toggle.\n@param id {String} The area id.\n@param enabled {Boolean} `true` to serve it, `false` to switch it off.\n@param options {Object} `{reloadTiles}` — defaults to `true`.",
        "type": "function(id, enabled, options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.setAreaEnabled",
        "examples": [
          "ExtjsUtils.OFFLINE.setAreaEnabled(areaId, false);"
        ],
        "returnDescription": "The saved record."
      },
      {
        "key": "setMaxZoom",
        "description": "Sets — or, with `null`, clears — the \"real max zoom\" of a WMS map at runtime: past it\nthe map keeps requesting the max-zoom tile and stretches it, so what the user sees\nand what an offline area downloads (zooms above the limit collapse onto it) change at\nonce. A raster whose detail ends at some zoom (`suggestMaxZoom`) downloads far fewer\ntiles this way. XYZ/OSM basemaps cannot be limited (`describeCacheableLayers` →\n`canLimitMaxZoom`).\n@param target {Object|String} A `{layer, kind}` entry from `getCacheableLayers`, or the map name.\n@param zoom {Number|null} The deepest zoom to request, or `null` for no limit.",
        "type": "function(target, zoom) : Number|null",
        "qualifiedName": "ExtjsUtils.OFFLINE.setMaxZoom",
        "examples": [
          "ExtjsUtils.OFFLINE.setMaxZoom(\"CSR:planet_brasil_2024\", 15);"
        ],
        "returnDescription": "The limit now in force (`getMaxZoom`)."
      },
      {
        "key": "setServingPolicy",
        "description": "Sets the serving policy (see `getServingPolicy`), tells the worker — which applies\nit from the next request on, no page reload — and then asks the map to request\nits tiles again (`reloadMapTiles`), so the screen shows the new mode at once\ninstead of at the next pan or zoom. Pass `reloadTiles: false` to only store the\npolicy (e.g. right before a download).\n@param policy {Object} `{mode, forceOffline, reloadTiles}` — `mode` is `\"CacheFirst\"`, `\"NetworkFirst\"`, `\"CacheOnly\"` or `\"NetworkOnly\"` (legacy kebab-case accepted); `forceOffline` (default `false`) makes the worker treat the network as down regardless of `navigator.onLine` — any call without it turns simulated offline back off; `reloadTiles` defaults to `true`.",
        "type": "function(policy) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.setServingPolicy",
        "examples": [
          "ExtjsUtils.OFFLINE.setServingPolicy({ mode: \"CacheFirst\" });",
          "// store only; the tiles on screen keep what they show\nExtjsUtils.OFFLINE.setServingPolicy({ mode: \"NetworkOnly\", reloadTiles: false });",
          "// simulate being offline, without DevTools throttling\nExtjsUtils.OFFLINE.setServingPolicy({ mode: \"CacheOnly\", forceOffline: true });"
        ],
        "returnDescription": "The stored policy `{mode, forceOffline, updatedAt}`."
      },
      {
        "key": "showAreaOutline",
        "description": "Outlines an area on the map — a dashed rectangle, e.g. the extent about to be\ndownloaded — or, with no extent, removes the outline. One outline at a time, on a\nvector layer of its own kept out of the layer list.\n@param extent {OpenLayers.Bounds|Array.<Number>|Object|null} The area, in map units; `null` removes the outline.",
        "type": "function(extent) : Boolean",
        "qualifiedName": "ExtjsUtils.OFFLINE.showAreaOutline",
        "examples": [
          "ExtjsUtils.OFFLINE.showAreaOutline(ExtjsUtils.LAYER.getLayerExtent(\"CSR:show_car\"));"
        ],
        "returnDescription": "`true` when an outline is shown."
      },
      {
        "key": "suggestMaxZoom",
        "description": "Suggests the finest zoom at which a raster layer still adds real detail, from the\npixel size its GetCapabilities abstract declares (the platform's rasters carry a\ngenerated \"Cell Width : N\" line; e.g. a 29.04 m cell size gives zoom 13 on the\nWeb Mercator series, so zoom 14+ is pure upsampling). Cell sizes below 1 are read\nas degrees and converted at the equator. It is a suggestion, not a setting: pass\n`result.maxZoom` to `layer.setMaxZoom()` (or put `maxZoom` in the layer config)\nto actually apply it, which shrinks the offline area (see `effectiveZoom`). The\nlayer's capabilities record must be loaded (wait for the \"local\" source).\n@param target {Object|OpenLayers.Layer} A `{layer, kind}` entry from `getCacheableLayers`, or the layer itself.",
        "type": "function(target) : Object|null",
        "qualifiedName": "ExtjsUtils.OFFLINE.suggestMaxZoom",
        "examples": [
          "ExtjsUtils.OFFLINE.getCacheableLayers().forEach(function(t) {\n    var suggestion = ExtjsUtils.OFFLINE.suggestMaxZoom(t);\n    if (suggestion && typeof t.layer.setMaxZoom === \"function\") t.layer.setMaxZoom(suggestion.maxZoom);\n});"
        ],
        "returnDescription": "`{cellSize, metersPerCell, maxZoom}`, or `null` when the abstract declares no cell size."
      },
      {
        "key": "updateServiceWorker",
        "description": "Asks the server for a newer service worker script (`registration.update()`) and\nactivates it (`activateLatestServiceWorker`). Never rejects.",
        "type": "function() : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.updateServiceWorker",
        "examples": [
          "ExtjsUtils.OFFLINE.updateServiceWorker().then(function(sw) {\n    console.log(sw.ok ? \"now on \" + sw.build : sw.error);\n});"
        ],
        "returnDescription": "The `activateLatestServiceWorker` result plus `{updated: true}`, or `{ok: false, error}`."
      },
      {
        "key": "useDefinitions",
        "description": "Chooses where THIS page loads its catalog (the WMS GetCapabilities) from: a named\ndefinitions cache, or — with `null` or `\"live\"` — the live server. Only this page is affected:\nevery other Mappia instance keeps its own choice. The catalog is reloaded at once,\nand the query's layers wait for it (`QUERY.sourceLoading`), so a query can make\nthe choice in its `&&` chain before its layer list — e.g. to start from a cached\ncatalog when the map server is unreachable. To choose before the page's first\ncatalog load, open the map page with `?definitions=<name>` instead. Only the\nservice worker answers from a cache, so a cache needs this page controlled by it.\n@param name {String|null} The cache name or id (`listDefinitions`), or `null` / `\"live\"` for the live catalog.",
        "type": "function(name) : Promise.<(String|null)>",
        "qualifiedName": "ExtjsUtils.OFFLINE.useDefinitions",
        "examples": [
          "// in a query: layers built from the cached catalog \"fazenda-42\"\nExtjsUtils.OFFLINE.useDefinitions(\"fazenda-42\") && [\n    { name: \"CSR:cultivo_cafe_cafe\", title: \"Café\", visibility: true }\n]"
        ],
        "returnDescription": "Resolves with the choice once the catalog has reloaded; rejects,\n changing nothing, for a cache when no service worker controls this page."
      },
      {
        "key": "verifyAreaServed",
        "description": "Proves an area is really served offline. It re-requests a sample of the URLs in\nthe area's cache through the page's normal fetch path (bypassing the HTTP cache),\nso the service worker sees them like any tile request, and counts the responses\nthe worker stamped as coming from this area's bucket. Without a controlling\nworker nothing can be served from the cache at all — `controlled: false` says so\n(on localhost the worker is off unless the service-worker config enables it, and\na freshly registered worker only controls the page from its next load). It also\nchecks coverage against the bucket's own metadata: re-enumerates the tiles the\nrecorded extent, zoom range and layers imply — with the live layers, so the same\nquery must be loaded — and reports which are missing from the cache.\n@param id {String} The area id.\n@param options {Object} Verification options.\n@param options.sampleSize {Number} How many cached URLs to re-request; `0` checks every entry.\n@param options.signal {AbortSignal} Aborts the sample requests.\n@param options.coverage {Boolean} `false` to skip the coverage re-enumeration.",
        "type": "function(id, options) : Promise.<Object>",
        "qualifiedName": "ExtjsUtils.OFFLINE.verifyAreaServed",
        "examples": [
          "ExtjsUtils.OFFLINE.verifyAreaServed(areaId, {sampleSize: 30}).then(function(result) {\n    if (!result.controlled) console.warn(\"no service worker controls this page yet\");\n    console.log(result.servedFromCache + \"/\" + result.checked + \" served from the area cache\");\n    if (result.coverage) console.log(result.coverage.missing + \" expected tiles missing\");\n});"
        ],
        "returnDescription": "`{id, cacheName, controlled, metadata, cachedEntries, checked, servedFromCache, servedByNetwork, failed, entries: [{url, status, servedFrom}], coverage: {layersFound, expected, present, missing, missingSample} | null}`."
      },
      {
        "key": "whenLayersReady",
        "description": "Resolves once the query's layers are on the map: the remote WMS sources the query\nregistered have loaded their capabilities (`QUERY.sourceLoading`) and the layer\nfiles have loaded (`QUERY.layerLoading`). The page's own sources (the \"local\"\ncatalog) are already loaded by then — the platform applies a query only after\nevery source is ready. Call it before\n`getCacheableLayers`/`selectCacheableLayers` in code that runs right after a query\nis applied — earlier, a `{name: \"CSR:...\"}` layer may simply not be on the map yet\nand an area downloaded then would silently miss it. `downloadArea` and\n`downloadAreaAsZip` wait for it themselves unless given `layers`. Never rejects:\nafter `timeoutMs` it resolves with whatever loaded.\n@param timeoutMs {Number} Longest wait, in milliseconds.",
        "type": "function(timeoutMs) : Promise",
        "qualifiedName": "ExtjsUtils.OFFLINE.whenLayersReady",
        "examples": [
          "ExtjsUtils.OFFLINE.whenLayersReady().then(function() {\n    console.log(ExtjsUtils.OFFLINE.getCacheableLayers().length + \" cacheable layers\");\n});"
        ],
        "returnDescription": "Resolves (with no value) when the layers are ready or the time is up."
      }
    ],
    "CAPABILITIES": [
      {
        "key": "_information",
        "description": "Prunes a WMS GetCapabilities document to the maps a query actually uses. It is what ?options=capabilities serves per query and what the offline engine caches.\nUsage: ExtjsUtils.CAPABILITIES",
        "name": "CAPABILITIES · catalogue filter (options=capabilities)"
      },
      {
        "key": "filterDocument",
        "description": "Prunes a parsed WMS GetCapabilities `Document` in place so it only describes\n`mapNames`: every nested `<Layer>` not in the list is dropped (the kept ones are\nre-appended in the list's order), then every WMS-C `<TileSet>` of another layer,\nthen every top-level `<SRS>` no remaining layer (nor the map) uses. This is the\nsame pruning the editor applies when a query is saved, and what the offline\nengine applies before caching the live capabilities.\n@param xmlDoc {Document} The parsed GetCapabilities XML document; it is mutated.\n@param mapNames {Array.<String>} WMS layer names to keep (see `getMapNamesInUse`).\n@param projectionCode {String} Map projection (e.g. `\"EPSG:900913\"`), kept in the SRS list even if no layer declares it.",
        "type": "function(xmlDoc, mapNames, projectionCode) : Document",
        "qualifiedName": "ExtjsUtils.CAPABILITIES.filterDocument",
        "examples": [
          "var map = ExtjsUtils.JS.getMap();\nvar xmlDoc = new OpenLayers.Format.XML().read(capabilitiesXmlText);\nExtjsUtils.CAPABILITIES.filterDocument(xmlDoc, ExtjsUtils.CAPABILITIES.getMapNamesInUse(map), map.projection);"
        ],
        "returnDescription": "The same document, pruned."
      },
      {
        "key": "filterText",
        "description": "`filterDocument()` for XML text in and text out: parses `xmlText`, prunes it to\n`mapNames` and serializes the result back to a string. Handy when the capabilities\ncame from a fetch/XHR response and must be stored or served as text again.\n@param xmlText {String} The raw GetCapabilities XML text.\n@param mapNames {Array.<String>} WMS layer names to keep (see `getMapNamesInUse`).\n@param projectionCode {String} Map projection (e.g. `\"EPSG:900913\"`), kept in the SRS list even if no layer declares it.",
        "type": "function(xmlText, mapNames, projectionCode) : String",
        "qualifiedName": "ExtjsUtils.CAPABILITIES.filterText",
        "examples": [
          "var map = ExtjsUtils.JS.getMap();\nfetch(ExtjsUtils.REQUEST.getGeoserverBaseUrl() + \"/wms?SERVICE=WMS&REQUEST=GetCapabilities&VERSION=1.1.1\")\n    .then(function(response) { return response.text(); })\n    .then(function(xmlText) {\n        var pruned = ExtjsUtils.CAPABILITIES.filterText(xmlText,\n            ExtjsUtils.CAPABILITIES.getMapNamesInUse(map), map.projection);\n        console.log(pruned.length + \" bytes after pruning\");\n    });"
        ],
        "returnDescription": "The serialized, pruned document."
      },
      {
        "key": "getMapNamesInUse",
        "description": "Lists the WMS map names a map currently uses: each layer's `LAYERS`\nparameter (comma-separated, e.g. `\"CSR:a,CSR:b\"`) plus its `otherNames`\n(`LayersProperties.otherNames` — maps the query needs at runtime but does not\ndraw initially). Layers without a `LAYERS` parameter (XYZ/OSM/vector) contribute\nnothing. The result is unique, in first-seen order. Use it to know which maps a\ncapabilities document must keep, or simply to enumerate the WMS maps on screen.\n@param map {OpenLayers.Map} The map whose layers are inspected (normally `ExtjsUtils.JS.getMap()`).",
        "type": "function(map) : Array.<String>",
        "qualifiedName": "ExtjsUtils.CAPABILITIES.getMapNamesInUse",
        "examples": [
          "var names = ExtjsUtils.CAPABILITIES.getMapNamesInUse(ExtjsUtils.JS.getMap());\nconsole.log(names); // [\"CSR:estados\", \"CSR:cultivo_cafe_cafe\"]"
        ],
        "returnDescription": "Unique WMS layer names, e.g. `[\"CSR:estados\", \"CSR:cultivo_cafe_cafe\"]`."
      }
    ]
  },
  "External Libraries": {
    "_information": {
      "title": "Other globals and libraries",
      "description": "Globals outside ExtjsUtils that a query can use: a vector overlay built from code (Ext.LayerAdditional), the script loader, the interface texts (Lang), mobile detection, and the bundled Highcharts library."
    },
    "LayerAdditional": [
      {
        "key": "_information",
        "completeAPI": "View the complete LayerAdditional <a href='/api/#category_LayerAdditional'>API here</a>.",
        "description": "A private vector layer kept out of the layer tree, for markers, highlights and geometry pushed from the parent page.\nUsage: var overlay = new Ext.LayerAdditional({}, ExtjsUtils.JS.getMap()); overlay.createLayer(); overlay.addFeatures(features);",
        "name": "Ext.LayerAdditional · overlay layer built from code"
      },
      {
        "key": "LayerAdditional",
        "description": "`new Ext.LayerAdditional(config, map)` — a private vector overlay: an\n`OpenLayers.Layer.Vector` that is added to the map but deliberately kept out of the\nlayer tree (`displayInLayerSwitcher: false`), for markers, highlights, search results\nand geometry pushed in from a parent page. Use it when something must render on the\nmap without becoming a user-visible, toggleable tree entry; for ordinary user-facing\ndata prefer a declarative `source: \"file\"` layer. The backing layer is created lazily\nby `createLayer()` (or by `drawFeature()`), and features added before that are\nbuffered and flushed on creation. It does not follow the visibility of any other\nlayer on its own.\n\nConstructor arguments: `config` — an object with the optional keys `listeners`\n(an Ext listeners object for the events below) and `layerConfig` (when given, the\nlayer is created immediately with `createLayer(layerConfig, layerConfig.async)`);\n`map` — the `OpenLayers.Map` to draw on, normally `ExtjsUtils.JS.getMap()` /\n`app.mapPanel.map`.\n\nEvents (Ext.util.Observable): `addedLayer` — fired with `{layer}` right after the\nvector layer is created and added to the map; `removedLayer` — fired with `{layer}`\nby `removeLayer()` right after the layer is removed from the map.\n\nPublic properties: `vectorLayer` — the `OpenLayers.Layer.Vector`, `null` until\ncreated; `map` — the map given to the constructor.\n@param config {Object} Optional configuration: `listeners` and/or `layerConfig` as described above; pass `{}` for none.\n@param map {OpenLayers.Map} The map the overlay is drawn on.",
        "type": "function(config, map)",
        "qualifiedName": "Ext.LayerAdditional",
        "examples": [
          "// a reusable highlight overlay driven by parent-page messages\nExtjsUtils.QUERY.setQueryGlobalProperties({\n    highlightLayer: null,\n    ensureHighlightLayer: function() {\n        if (!window.highlightLayer) {\n            window.highlightLayer = new Ext.LayerAdditional({}, ExtjsUtils.JS.getMap());\n            window.highlightLayer.createLayer({\n                styleMap: new OpenLayers.StyleMap({\n                    \"default\": new OpenLayers.Style({fillColor: \"#ffcc33\", fillOpacity: 0.35, strokeColor: \"#ff6600\", strokeWidth: 2, pointRadius: 6, graphicName: \"circle\"})\n                })\n            });\n        }\n        return window.highlightLayer;\n    },\n    showHighlight: function(geojson) {\n        var overlay = window.ensureHighlightLayer();\n        overlay.removeFeatures(); // no argument: clear everything\n        overlay.addFeatures(ExtjsUtils.GEOJSON.geojson2Features(geojson));\n    }\n}) && ExtjsUtils.QUERY.setMappiaIoCallback(function(message) {\n    if (message && message.type === \"geojson\") window.showHighlight(message.msg);\n}) && [\n    { name: \"CSR:estados\", title: \"Estados\", visibility: true }\n]",
          "// listen to the layer lifecycle\nvar overlay = new Ext.LayerAdditional({\n    listeners: { addedLayer: function(evt) { console.log(\"created\", evt.layer.id); } }\n}, ExtjsUtils.JS.getMap());"
        ]
      },
      {
        "key": "addFeatures",
        "description": "Adds one feature or an array of features to the overlay. When the layer does not\nexist yet the features are buffered and added as soon as `createLayer` runs, so it\nis safe to call in any order. Features must be in the map projection — build them\nwith `createPoint`/`createFeature`, or convert GeoJSON with\n`ExtjsUtils.GEOJSON.geojson2Features`.\n@param features {OpenLayers.Feature.Vector|Array.<OpenLayers.Feature.Vector>} The feature(s) to add.",
        "type": "function(features)",
        "examples": [
          "overlay.addFeatures(new OpenLayers.Feature.Vector(overlay.createPoint(-44.0, -20.5, \"EPSG:4326\"), {name: \"Fazenda\"}));"
        ]
      },
      {
        "key": "createFeature",
        "description": "Wraps one geometry, or an array of geometries (combined into an\n`OpenLayers.Geometry.Collection`), in a vector feature you can then style and\nadd with `drawFeature`/`addFeatures`.\n@param geometries {OpenLayers.Geometry|Array.<OpenLayers.Geometry>} The geometry, or several geometries drawn as one feature.",
        "type": "function(geometries) : OpenLayers.Feature.Vector",
        "examples": [
          "var feature = overlay.createFeature(overlay.createPoint(-44.0, -20.5, \"EPSG:4326\"));\nfeature.attributes.name = \"Fazenda\";\noverlay.addFeatures(feature);"
        ],
        "returnDescription": "The new feature (no attributes, no style)."
      },
      {
        "key": "createImageTile",
        "description": "Builds a feature representing an image tile: a rectangle covering `bbox` plus a\npoint at its centre, combined in one geometry collection. Style it with\n`externalGraphic` (the image URL) and `pointRadius` (half the image size) to draw\na picture at the centre of the box, e.g. a thumbnail or a symbol over an area.\nReturns `undefined` (and logs) when `bbox` does not have exactly four numbers.\n@param bbox {Array.<Number>} The tile bounds as `[left, bottom, right, top]`.\n@param projection {String|OpenLayers.Projection} The projection `bbox` is expressed in.",
        "type": "function(bbox, projection) : OpenLayers.Feature.Vector|undefined",
        "examples": [
          "var tile = overlay.createImageTile([-44.1, -20.6, -43.9, -20.4], \"EPSG:4326\");\noverlay.drawFeature(tile, {externalGraphic: \"/theme/app/img/logo.png\", pointRadius: 24, strokeColor: \"#666666\", fillOpacity: 0});"
        ],
        "returnDescription": "The tile feature, or `undefined` for an invalid bbox."
      },
      {
        "key": "createLayer",
        "description": "Creates the backing vector layer, once: the call is ignored when the layer already\nexists or its creation is pending, and the configuration of an existing layer is\nnot updated. The layer gets a generated id, `displayInLayerSwitcher: false` and the\n`initialVisibility`, is added to the map, `addedLayer` is fired and the features\nbuffered by `addFeatures` are flushed into it. When `layerConfig` is omitted a\nbuilt-in grey/white `styleMap` for points, lines and polygons is used. Creation is\nsynchronous by default; pass `async: true` to defer it to a short timeout (useful\nwhen called from inside another map event).\n@param layerConfig {Object} `OpenLayers.Layer.Vector` options — typically `{styleMap: new OpenLayers.StyleMap({...})}`; omitted ⇒ the default style.\n@param async {Boolean} `true` to create the layer asynchronously (via `setTimeout`); `false`, the default, creates it immediately.",
        "type": "function(layerConfig, async)",
        "examples": [
          "var overlay = new Ext.LayerAdditional({}, ExtjsUtils.JS.getMap());\noverlay.createLayer({\n    styleMap: new OpenLayers.StyleMap({\n        \"default\": new OpenLayers.Style({fillColor: \"${color}\", fillOpacity: 0.4, strokeColor: \"#333333\", strokeWidth: 1}),\n        \"select\": new OpenLayers.Style({strokeColor: \"#ff0000\", strokeWidth: 3})\n    })\n});"
        ]
      },
      {
        "key": "createPoint",
        "description": "Creates a point geometry from coordinates given in `projection` and transforms it\ninto the map projection, ready to be wrapped in a feature or used as a polygon\nvertex. For a longitude/latitude pair pass `\"EPSG:4326\"`.\n@param x {Number} The horizontal coordinate (longitude for EPSG:4326).\n@param y {Number} The vertical coordinate (latitude for EPSG:4326).\n@param projection {String|OpenLayers.Projection} The projection `x`/`y` are expressed in (see `resolveProjection`).",
        "type": "function(x, y, projection) : OpenLayers.Geometry.Point",
        "examples": [
          "var marker = new OpenLayers.Feature.Vector(overlay.createPoint(-46.63, -23.55, \"EPSG:4326\"));\noverlay.addFeatures(marker);"
        ],
        "returnDescription": "The point, in the map projection."
      },
      {
        "key": "createPolygon",
        "description": "Creates a polygon geometry with a single ring whose vertices are the given points\n(in order; the ring is closed automatically).\n@param points {Array.<OpenLayers.Geometry.Point>} The vertices, already in the map projection (e.g. from `createPoint`).",
        "type": "function(points) : OpenLayers.Geometry.Polygon",
        "examples": [
          "var square = overlay.createPolygon([\n    overlay.createPoint(-44.1, -20.4, \"EPSG:4326\"), overlay.createPoint(-43.9, -20.4, \"EPSG:4326\"),\n    overlay.createPoint(-43.9, -20.6, \"EPSG:4326\"), overlay.createPoint(-44.1, -20.6, \"EPSG:4326\")\n]);\noverlay.drawFeature(overlay.createFeature(square), {fillColor: \"#00ff00\", fillOpacity: 0.2});"
        ],
        "returnDescription": "The polygon."
      },
      {
        "key": "drawFeature",
        "description": "Styles and draws a feature in one call: creates the layer if needed\n(`createLayer()` with the default style), sets `feature.style` to `style`\ncompleted with OpenLayers' default symbolizer, and adds the feature. A per-feature\nstyle set this way takes precedence over the layer's `styleMap`.\n@param feature {OpenLayers.Feature.Vector} The feature to draw (see `createFeature`).\n@param style {Object} OpenLayers symbolizer properties (`fillColor`, `fillOpacity`, `strokeColor`, `strokeWidth`, `pointRadius`, `externalGraphic`, `label`, ...); omitted ⇒ the layer's styleMap applies.",
        "type": "function(feature, style)",
        "examples": [
          "var point = overlay.createFeature(overlay.createPoint(-44.0, -20.5, \"EPSG:4326\"));\noverlay.drawFeature(point, {pointRadius: 8, fillColor: \"#ff0000\", strokeColor: \"#ffffff\", strokeWidth: 2});"
        ]
      },
      {
        "key": "drawLatLongExtents",
        "description": "Draws a highlighted rectangle for a lon/lat bounding box — the way the interface\noutlines a layer's extent: two stacked rectangles, a wide white border with a\ntranslucent fill under a thin coloured border. Creates the layer if needed. Logs\nand does nothing when `llbbox` does not have exactly four numbers.\n@param llbbox {Array.<Number>} The bounds in EPSG:4326 as `[left, bottom, right, top]` (west, south, east, north), e.g. a record's `llbbox`.\n@param color {String} Fill colour (and, by default, the thin border colour `#FFC000` is used).",
        "type": "function(llbbox, color)",
        "examples": [
          "var record = ExtjsUtils.LAYER.getLayersRecord(\"CSR:estados\");\noverlay.drawLatLongExtents(record.get(\"llbbox\"), \"#ff8800\");"
        ]
      },
      {
        "key": "map",
        "description": "The `OpenLayers.Map` the overlay draws on, as given to the constructor.",
        "type": "OpenLayers.Map"
      },
      {
        "key": "removeFeatures",
        "description": "Removes features from the overlay — the given feature or array of features, or\nevery feature when the argument is omitted (the usual \"clear the highlight\" call).\nWorks before the layer exists too, by dropping them from the pending buffer.\n@param features {OpenLayers.Feature.Vector|Array.<OpenLayers.Feature.Vector>} The feature(s) to remove; omit to remove all.",
        "type": "function(features)",
        "examples": [
          "overlay.removeFeatures();            // clear everything\noverlay.removeFeatures(oneFeature);  // remove a single feature"
        ]
      },
      {
        "key": "removeLayer",
        "description": "Removes the vector layer from the map (firing `removedLayer`), cancels a pending\nasync creation and forgets the layer, so the next `createLayer`/`drawFeature`\ncreates a fresh one. To only clear the drawn features and keep the layer, use\n`removeFeatures()` instead.",
        "type": "function()",
        "examples": [
          "overlay.removeLayer();"
        ]
      },
      {
        "key": "resolveProjection",
        "description": "Normalizes a projection given as an EPSG string (`\"EPSG:4326\"`), an\n`OpenLayers.Projection` or any object with `getCode()` into an\n`OpenLayers.Projection`, falling back to the map projection when it is missing.\nDelegates to `ExtjsUtils.PROJECTION.resolveProjectionObject`; `createPoint` uses it,\nso you rarely need to call it yourself.\n@param projection {String|OpenLayers.Projection|Object} The projection to resolve; falsy ⇒ the map projection.",
        "type": "function(projection) : OpenLayers.Projection",
        "examples": [
          "var proj = overlay.resolveProjection(\"EPSG:4326\");\nconsole.log(proj.getCode()); // \"EPSG:4326\""
        ],
        "returnDescription": "The resolved projection object."
      },
      {
        "key": "setOverLayer",
        "description": "Makes sure the overlay is stacked above `baseLayer`: moves the vector layer's index\nto at least that layer's index (`map.setLayerIndex`). Call it after `createLayer`\n(or after starting an async creation); the reorder happens on a short timeout to\nwork around an OpenLayers/Ext re-entrancy bug when called from inside an event, so\nit is not effective synchronously.\n@param baseLayer {OpenLayers.Layer} The layer that must end up below the overlay.",
        "type": "function(baseLayer)",
        "examples": [
          "overlay.createLayer();\noverlay.setOverLayer(ExtjsUtils.LAYER.getLayerByName(\"CSR:estados\"));"
        ]
      },
      {
        "key": "setVisibility",
        "description": "Shows or hides the overlay. Before the layer is created it only records the\ninitial visibility the layer will be created with; afterwards it calls\n`vectorLayer.setVisibility(state)`.\n@param state {Boolean} `true` to show the overlay, `false` to hide it.",
        "type": "function(state)",
        "examples": [
          "overlay.setVisibility(!MOBILE_UTILS.isMobile());"
        ]
      },
      {
        "key": "vectorLayer",
        "description": "The backing `OpenLayers.Layer.Vector` where the features are drawn. `null` until\n`createLayer` runs, then a real OpenLayers vector layer you may hand to other\ncontrols, e.g. `new OpenLayers.Control.CustomSelectFeature(overlay.vectorLayer, {...})`.",
        "type": "OpenLayers.Layer.Vector|null",
        "examples": [
          "overlay.createLayer();\noverlay.vectorLayer.events.register(\"featureselected\", null, function(evt) { console.log(evt.feature); });"
        ]
      }
    ],
    "AsyncLoader": [
      {
        "key": "_information",
        "completeAPI": "",
        "description": "Load one or more external resources by its URL and call a callback function when it finishes.\narray: {Array} A array of resource urls to load.\ncallback: Function that will be called when the load ends.\nUsage: AsyncLoader.loadScriptOnce({Array}, {Function}}",
        "name": "AsyncLoader · load scripts once"
      },
      {
        "key": "loadScriptOnce",
        "description": "Loads one or more external resources by URL, each of them only once per page, and\ncalls `callback` after every URL of the set has finished loading. A URL ending in\n`.css` is inserted as a `<link rel=\"stylesheet\">`; any other URL is inserted as an\nasync `<script>` tag (placed before the first script of the page). Use it inside a\nquery to lazy-load libraries or data files (Highcharts modules, PapaParse, a\npre-baked JS data file) right before the code that needs them.\n\nRules worth knowing: a URL that already finished loading is skipped, so repeated\ncalls are cheap and safe; when nothing in the set still needs loading the callback\nruns synchronously, before this function returns; calls made with the same set of\npending URLs share one loading queue, and all their callbacks run once that set\ncompletes; the callback receives no arguments.\n@param src {String|Array.<String>} A resource URL, or an array of URLs to load together.\n@param callback {function} Called (with no arguments) once every URL in `src` is loaded.",
        "type": "function(src, callback)",
        "examples": [
          "AsyncLoader.loadScriptOnce([\"/theme/app/js/papaparse.min.js\", \"/theme/app/js/highcharts/latest/sankey.js\"], function() {\n    // both files are loaded (or were already loaded): safe to use Papa and Highcharts.seriesTypes.sankey\n    drawSankeyChart();\n});",
          "AsyncLoader.loadScriptOnce(\"/theme/app/css/my-query-styles.css\");"
        ]
      }
    ],
    "Lang": [
      {
        "key": "_information",
        "description": "The interface translation dictionary (pt-BR by default, English with ?lang=eng). Queries read the same strings the interface shows, e.g. Lang.maps.",
        "name": "Lang · interface texts"
      },
      {
        "key": "LIST",
        "description": "The language codes the interface supports, in order: `['ptbr', 'eng']`. The first\none is the default when `?lang=` is absent or unknown; the others are the accepted\nvalues of the `lang` URL parameter.",
        "type": "Array.<String>",
        "qualifiedName": "Lang.LIST",
        "examples": [
          "Lang.LIST.indexOf(ExtjsUtils.REQUEST.getParameterByName(\"lang\")) !== -1"
        ]
      },
      {
        "key": "Lang",
        "description": "The interface translation dictionary, a global object whose keys are the strings the\ninterface shows (`Lang.maps`, `Lang.showLayerLegend`, `Lang.clearTransition`,\n`Lang.expandChart`, `Lang.layerDetails`, `Lang.yes`, `Lang.cancel`, ...). The values\nare Brazilian Portuguese by default; when the page is opened with `?lang=eng` the\nentries of `Lang.languages.eng` are applied over them at load time\n(`Lang.updateLang()`), so a query that reads `Lang.<key>` gets the same wording the\ninterface is using and follows the language switch for free. The key list is the\nobject itself (this file); read a key, do not assign to it.",
        "type": "Object",
        "qualifiedName": "Lang",
        "examples": [
          "// a tenant button whose caption follows the interface language\ndescriptionHtml: '{{button|id=legendBtn|text=' + Lang.showLayerLegend + '}}'",
          "var caption = Lang.getLang() === \"eng\" ? \"Coffee area\" : \"Área de café\";"
        ]
      },
      {
        "key": "formatMessage",
        "description": "Fills the `{placeholder}` slots of a message template with the values of `params`:\nevery `{name}` in `content` is replaced by `params.name`; a placeholder without a\nmatching key is left as it is. Several `Lang` messages are templates\n(e.g. `Lang.invalidLayerName` contains `{layerName}`), and a query can use the same\nmechanism for its own messages.\n@param content {String} The template text containing `{name}` placeholders (typically a `Lang` message).\n@param params {Object} Values keyed by placeholder name.",
        "type": "function(content, params) : String",
        "qualifiedName": "Lang.formatMessage",
        "examples": [
          "Lang.formatMessage(Lang.invalidLayerName, {layerName: \"CSR:estados\"});\n// 'O nome do mapa \"CSR:estados\" é inválido ou o mapa não foi carregado corretamente.'",
          "Lang.formatMessage(\"{count} features selected in {name}\", {count: 3, name: \"Minas Gerais\"});"
        ],
        "returnDescription": "The template with the placeholders replaced."
      },
      {
        "key": "getLang",
        "description": "Returns the language code the interface is currently using: `\"ptbr\"` (default) or\n`\"eng\"` when the page was opened with `?lang=eng`. Use it to pick language-specific\ncontent of your own (labels, URLs, number formats) consistently with the interface.",
        "type": "function() : String",
        "qualifiedName": "Lang.getLang",
        "examples": [
          "var chartTitle = Lang.getLang() === \"eng\" ? \"Deforestation by year\" : \"Desmatamento por ano\";"
        ],
        "returnDescription": "The current language code, one of `Lang.LIST`."
      },
      {
        "key": "lang",
        "description": "The language code currently applied, `\"ptbr\"` or `\"eng\"`; set by `updateLang()`\nat load time from the `?lang=` URL parameter. Read it through `Lang.getLang()`.",
        "type": "String",
        "qualifiedName": "Lang.lang",
        "examples": [
          "if (Lang.lang === \"eng\") { ... }"
        ]
      },
      {
        "key": "updateLang",
        "description": "Applies the language requested by the `?lang=` URL parameter: when it names a known\ntranslation (`Lang.languages.eng`) its entries are copied over the `Lang` keys, and\n`Lang.lang` is set to it; otherwise the default `ptbr` stays and `Lang.lang` is\n`\"ptbr\"`. The platform calls it once at load time, before any interface text is\nrendered; a query does not need to call it, but calling it again is harmless.",
        "type": "function()",
        "qualifiedName": "Lang.updateLang",
        "examples": [
          "Lang.updateLang();\nconsole.log(Lang.getLang()); // \"eng\" when the URL has ?lang=eng"
        ]
      },
      {
        "key": "verifySupportedBrowser",
        "description": "Shows the platform's \"browser not supported\" alert (`Lang.browserNotSupported`, via\n`ExtjsUtils.ALERTIFY.alert`) when `ExtjsUtils.CHECK.browserSupported()` says the\ncurrent browser is too old; does nothing otherwise. The interface calls it at\nstartup; a query may call it again before enabling a feature that needs a modern browser.",
        "type": "function()",
        "qualifiedName": "Lang.verifySupportedBrowser",
        "examples": [
          "Lang.verifySupportedBrowser();"
        ]
      }
    ],
    "MobileUtils": [
      {
        "key": "_information",
        "description": "Mobile layout detection shared by the desktop and mobile interfaces (global MOBILE_UTILS).\nUsage: MOBILE_UTILS.isMobile()",
        "name": "MOBILE_UTILS · mobile layout"
      },
      {
        "key": "isMobile",
        "description": "Tells whether the interface is currently rendered in its mobile layout. It reads the\n`--is-mobile` CSS variable that the responsive stylesheet sets from a media query\n(`1` when the viewport is narrower than 768px, `0` otherwise), so the answer follows\nthe viewport size and changes when the window is resized — re-check it inside a\n`resize` handler when a layout decision depends on it. This is distinct from\n`ExtjsUtils.CHECK.isMobile()`, which is device based (user agent / orientation).",
        "type": "function() : Boolean",
        "qualifiedName": "MOBILE_UTILS.isMobile",
        "examples": [
          "function layoutCharts() {\n    if (MOBILE_UTILS.isMobile()) {\n        // narrow screen: stack the chart under the map\n    } else {\n        // wide screen: keep the chart beside the map\n    }\n}\nlayoutCharts();\nExtjsUtils.addDomListener(window, \"resize\", layoutCharts);"
        ],
        "returnDescription": "`true` when the mobile layout is active."
      }
    ],
    "Highcharts": [
      {
        "key": "_information",
        "completeAPI": "Highcharts JS has a complete set of examples and a nice documentation that can be <a target='_blank' href='https://www.highcharts.com/demo'>accessed here</a>.",
        "description": "Highcharts is a library used to easly create interactive charts.\nUsage: (Highcharts.chart(DOM_ID, {});)",
        "name": "Highcharts · charts"
      },
      {
        "key": "chart",
        "description": "Highcharts is loaded by the calculator and editor pages, so a query can call\n`Highcharts.chart(...)` without loading anything: put a container in the layer's\n`descriptionHtml` and create the chart once the panel exists - in `onInputsReady`, in\n`beforeCalc` or in a widget handler - then update it as results arrive instead of\nrecreating it. `ExtjsUtils.HIGHCHART.getById(id)` gives the chart back from the\ncontainer's id, which is what makes the update possible from another callback.\n\nThe bundled build covers the standard chart types plus `highcharts-more`. Extra\nmodules are loaded on demand with `AsyncLoader.loadScriptOnce` (see the Sankey entry).\n@param renderTo {String} Id of the container element declared in `descriptionHtml`.\n@param options {Object} The Highcharts configuration object.",
        "type": "function(renderTo, options) : Object",
        "qualifiedName": "Highcharts.chart",
        "examples": [
          "descriptionHtml: '<div id=\"emissions_chart\" style=\"height:220px\"></div>',\nfunctions: {\n    drawChart: function (values) {\n        var chart = ExtjsUtils.HIGHCHART.getById('emissions_chart');\n        if (chart) { chart.series[0].setData(values); return; }\n        Highcharts.chart('emissions_chart', {\n            chart: { type: 'column' },\n            title: { text: 'Emissions by year' },\n            xAxis: { categories: ['2020', '2021', '2022'] },\n            series: [{ name: 'Mt', data: values }]\n        });\n    }\n}"
        ],
        "returnDescription": "The chart instance."
      }
    ],
    "Sankey": [
      {
        "key": "_information",
        "description": "Soon.",
        "name": "Sankey · flow charts (Highcharts module)"
      },
      {
        "key": "load",
        "description": "A Sankey diagram - transitions between categories drawn as flows - is a Highcharts\nmodule rather than part of the bundled build, so it is loaded on demand before the\nchart is created. Once the module is in place, a Sankey is an ordinary Highcharts\nchart with `type: 'sankey'`, whose data is a list of `[from, to, weight]` rows: one row\nper transition, which is exactly the shape a transition matrix produces.\n\nLoad it once, when the panel is ready, and draw inside the callback - the module is\nshared, so a second call with the same URL is free.",
        "type": "function() : undefined",
        "examples": [
          "AsyncLoader.loadScriptOnce('/theme/app/js/highcharts/latest/sankey.js', function () {\n    Highcharts.chart('transitions_chart', {\n        title: { text: 'Land use transitions' },\n        series: [{\n            type: 'sankey',\n            keys: ['from', 'to', 'weight'],\n            data: [['Forest', 'Pasture', 120], ['Pasture', 'Crop', 45]]\n        }]\n    });\n});"
        ],
        "returnDescription": "Nothing; the chart is created in the callback."
      }
    ]
  }
}