Pitfalls, and properties that do nothing
The mistakes that make a query fail silently, the keys that have been copied between queries for years without ever being read, and the handful of parameters the platform accepts but does not honour.
Mappia reference, section “Practical guidance”. Page: https://mappia.earth/reference/pitfalls/ Generated API entries: GroupProperties.openGroup, GroupFunctions.onClickViewGroup, URLOptions, LoadCsv.trim, SummedArea, Hoverpixel, LayersProperties.maxQntEntries, FileLayer.type, QUERY.setQueryGlobalProperties (https://mappia.earth/assets/api.json)
A query almost never fails loudly. It shows a map with one layer missing, or a panel with no widget, or a legend that never appears — and the cause is usually one of the items below. The list is short because it is the result of reading the whole body of queries running in production: these are the things that actually go wrong.
1. The query produced nothing
| Symptom | Cause | Fix |
|---|---|---|
| Only the default background is shown | The query text is not a single expression (a top-level var, a statement, a stray semicolon), so it threw before producing a value | See the query language §1 |
| Same, with no error in the console | The expression evaluated, but its value was not an array of layer definitions | End the expression with the array |
| One layer missing, others fine | Its name is not in the catalogue, or its server was registered after the array that uses it | Check the name; put addRemoteWMSServer before the array |
| A calculated layer stays empty | A map in name is not published with the operation the query asks for; the console says Operation X is not defined for layer Y | Use a decoding the map offers |
this.something is not a function | An arrow function where the platform binds this to the layer | Regular function for beforeCalc, handler, functions.*, the vector callbacks |
A value is undefined only inside expression | It came from a closure, a global or the DOM, none of which exist in the calculation worker | Compute it in beforeCalc, pass it as an input |
Cannot read properties of undefined (reading '<id>') from expression | inputs.id[ID] used inside expression: the inputs reach the worker as JSON, which keeps only the array’s elements, so the id map is gone | Read by position: inputs[0] is the first input widget in descriptionHtml. inputs.id[ID] works only on the page (beforeCalc, functions, …) |
filterLegends[...].color.join is not a function | setCalculateLegend given a CSS colour ("#c0392b"): the legend entries take [R, G, B] | { color: [192, 57, 43], value: 1, title: "Above 800 m" }. Layer and group color properties do take CSS colours |
| An input changes, the countdown runs out, the map stays the same | beforeCalc called setCalculateLegend with the same legend as before: an unchanged legend skips the redraw, so the new inputs never reach the tiles | Make the legend follow the inputs - list only the classes shown, or put the value in a title ("Above " + value + " m"), as the documentation examples do |
| Works once, then breaks on re-apply | State kept outside the query’s globals | setQueryGlobalProperties |
| “Global variable can’t be redefined” | A global name collides with one the page already had | Rename it |
| A widget’s tag renders as nothing | Unknown tag name; the console shows ` INVALID OBJECT NAME` | Use the exact tag from the widget markup language |
2. Keys that are silently ignored
These appear in real queries — some in dozens of them — and have no reader in the platform. Nothing warns about them, which is exactly why they spread by copy-paste. Remove them; they document an intent the map never had.
| Key | Where it is written | What people expect | What happens |
|---|---|---|---|
closedGroup | On a group | Start the group collapsed | Nothing. Groups start collapsed by default; use openGroup: true to start one expanded |
disableDownload | On a layer | Hide that layer’s download button | Nothing. It only exists as a page option (options=disabledownload in the URL), which applies to the whole page |
hideBottomButton | On a layer | Hide the query button under the layer row | Nothing at layer level. It works only inside a paramsButtonConfig entry of type query |
legendId | On a layer | Point the layer at a legend | Nothing. It is a parameter of the legendhtml widget |
maxZoomReal | On a layer | A second maximum zoom | Nothing. maxZoom is the one that is read |
showTimelineButton, timelineConfig | On a layer | Configure a timeline | Nothing. The timeline is the `` widget, configured by its own parameters |
priority, visible, layerGroup, startOpen, startOpened | On a group | Ordering and initial state | Nothing at group level. priority and visibility are layer properties; openGroup is the group one |
popupTemplate, popupCallback | On a file (vector) layer | A popup with the clicked feature’s attributes | Nothing. They are accepted and never read - no popup opens. Show the attributes from onClick, e.g. with ExtjsUtils.ALERTIFY.log |
selectSource | Anywhere | Choose a source per layer name | Nothing. No part of the platform reads it |
3. Accepted, but not honoured
Different case: the platform takes the value, and then does not do what the name promises.
| Parameter | Reality |
|---|---|
| `` | The flag is passed to the CSV parser, which never reads it. Trim the cells yourself when you use them |
maxQntEntries on a calculated layer | Stored, and clamped to the palette size, but the legend grouping uses its own default instead. To control the entries exactly, build the legend yourself with setCalculateLegend |
| `` input value | Never filled. The sums reach runOnClick, not inputs.id[ID] |
| `` | Moving or clicking never recalculates the layer. Its lastInfo value is always current, so read it in a callback — or force a recalculation yourself |
onClickViewGroup / onToggleViewGroup on a layer | They only fire for a real group row (a viewTitle node). On a layer that is not inside a group, they bind to the invisible root and never run |
4. Embedding
| Mistake | Effect |
|---|---|
noopener or noreferrer on the iframe or the opened window | Messages between the parent and the map stop working |
Re-using an iframe after tearing the connection down, without setting src again | The parent-side helper is gone; the map never answers |
| Treating one integration’s operation names as platform API | Operation names are a contract between one parent page and one query, not a platform feature |
5. Data
| Mistake | Effect |
|---|---|
GeoJSON without a crs (and no fromProj on the layer) | The data is assumed to be in the platform’s default projection, and silently lands in the wrong place |
| Coordinates in latitude/longitude order where the format wants longitude/latitude | Features appear mirrored around the diagonal, or off the map |
| A CSV whose numeric column arrives as text | Comparisons in expression behave like string comparisons; convert before comparing |
6. Style
Two habits worth dropping:
- A full setup chain on every query.
[{ name: "…" }]is a complete query. AddsetOptions,decorate,setQueryGlobalPropertiesonly where the map needs them. - Code hidden in a made-up
decoratekey. Any key thatdecoratedoes not know is installed as a CSS rule, so writingnoTop: (function () { … })()“works” — the function runs while the object is being built, and its return value becomes a stylesheet entry. Userunfor code and keepdecoratefor chrome.
7. Layers age
The most frequent cause of an old query breaking is not the query: a published map was renamed or withdrawn, and the layer that referenced it now resolves to nothing. When reviving an old map, check its layer names against the current catalogue before looking for anything else.