Z order control during map production - STEMLab/geotools GitHub Wiki
- Contact: aaime
- Tracker: https://osgeo-org.atlassian.net/browse/GEOT-5179
- TLDR: The real world has things above, things below, and does not care about data source native order
Description
Currently z ordering in map rendering is controlled by two main elements:
- The layer ordering in a
MapContent
object - The
FeatureTypeStyle
list in a SLD style, that is most often used to get drawing effects such as road casing, but can also be leverage to control z ordering of elements, assuming the number of z levels is small (one level perFeatureTypeStyle
)
Both functionalities are largely used, their behavior mandated by standards, as such we cannot perform modifications.
Now we want to introduce the ability to z-order elements based on an attribute, and possibly doing so cross layer, to control the order in which features are displayed so that it matches the real-world order of the same features. Examples are road networks, with underpasses and overpasses (single layer z-order), but also cases in which we have elements coming from different layers, such as roads and rails.
A new, more flexible way to apply data driven ordering, withing a single layer, or across multiple layers, is required.
Single layer z-ordering
Ordering features within a single layer poses no threats to backwards compatibiliy, it’s a simple matter of sorting the features in the desired order, instead of the random one the feature source is using by default.
Stealing a page from the WFS GetFeature syntax, we can add a sortBy
vendor option at the FeatureTypeStyle
level that would order the features as desired, for example:
<FeatureTypeStyle>
…
<VendorOption name=”sortBy”>attribute1 A,attribute2 D</VendorOption>
</FeatureTypeStyle>
A
and D
are optional, and are the sort direction markers (Ascending, Descending, respectively).
Implementation wise, this will turn into a sortBy clause in the GeoTools Query
, leaving the data source to perform the ordering (which will be more or less efficient depending on its nature, presence of indexes, and so on).
In case there are multiple feature type styles for the same layer (e.g. road casing style), and only one of them a sortBy definition, they will all be loaded under that sortBy, under the notion that whatever order is fine for those that are not marked with sortBy.
In case instead there are multiple different sortBy, the algorithm will assign the sortBy to those that do not have a sortBy assigned based on the latest sortBy found (or the first incoming one in the list, in case none was found so far), and groups with different sortBy will painted separately (just as groups with a different rendering transformation).
Finally, the relationship between sortBy and rendering transformation needs to be understood: is sortBy a feature reading tool, thus to be applied on the original feature source, or a rendering tool, thus to be applied on the transformed features?
Considering that applying it to the transformed features would disallow the obvious optimization of using native indexes, and that it would require some sort of in memory ordering (or disk based ordering) we suggest that we interpret is as a reading tool, and thus, apply it to the data retrieval query, before the rendering transformation is applied.
Cross layer z-ordering
Cross layer ordering is required to adjust the relative position of features that are actually stacked in the real world, but poses several problems:
- To some extent breaks the MapContent/WMS GetMap model, in which the layer order controls the z-order (painter’s model)
- Requires significant changes in the renderer, as it will require us to paint a sequence of features from different layers (as opposed to switching type of feature once per layer)
- Cannot rely anymore on the store own ordering (at least, not completely)
Point number 2. cannot really be avoided, but we’ll probably create a custom code path for this cross-layer sorting where the current layer can change feature by feature.
Point number 1. is tricky, a map rendering can contain multiple layers, some won’t have a z-ordering, others will do but they will tend to group differently with each other, plus, we cannot fully override the user request in layer position.
We thus propose to mark the FeatureTypeStyle subject to cross layer z-ordering with a group marker:
<FeatureTypeStyle>
…
<VendorOption name=”sortByGroup”>linesGroup</VendorOption>
<VendorOption name=”sortBy”>attribute1 A,attribute2 D</VendorOption>
</FeatureTypeStyle>
FeatureTypeStyles are going to be merged into a single sorting group provided that:
- They are side by side in the map request (expanding the list of all styles)
- They share the same sortByGroup
- The sortBy definition has the same number of attributes, type, and direction (no need to have the same names)
When these conditions are met, a cross layer rendering group will be created and all the features will be rendered according the sortBy global ordering. In case layers in the same group are not uniform sortBy wise, an exception will be thrown.
Implementation wise, each source will be sorted using its native mechanism, and then read in memory if there are less than 1000 features, or dumped on disk using the same mechanism as the MergeSortReader if the size is exceeded, to avoid memory bound-ness. Then, these memory/disk sources will be merge-sorted by always picking the smallest feature from the current lot of available features, starting from the first layer in the group, in order to have the natural layer order be the tie breaker in case multiple features have the same ordering attribute values.
As the final point, we need to understand the relationship between this sorted reading/rendering, and the FeatureTypeStyle render ordering, that is often used to get visual effects such as road casing. In case a layer is associated to multiple feature type styles, we need to ensure that the visual effect (road casing) is not broken by the sorting implementation.
To this end, we can leverage the sorted reader memory/local disk storage nature and allow marking, thus rendering in the following way:
- Mark the current position in the sorted reader
- Keep on reading features as long as they are coming from the same layer and render them with the current feature type style
- Once a feature in a different layer is found, reset to the mark and process all the subsequent feature type style for the current layer, in order, until we are done
- Apply a new mark and restart from the beginning
- Repeat until all features are read
The layers will be marked with a unique id before dealing with the feature type style merging, so if a layer shows up multiple times in the same request, each instance will be considered as a separate layer as far as the above algorithm is concerned.
CSS support
CSS wise the z-index property is already used to drive FeatureTypeStyle generation, thus, for backwards compatibility, we should resist the temptation to just make that property attribute-dependent.
* {
…
sortBy: zIndex;
sortByGroup: lines;
}
Status
Choose one of:
- Under Discussion
- In Progress
- Completed
- Rejected,
- Deferred
Voting:
- Andrea Aime +1
- Ben Caradoc-Davies +0
- Christian Mueller
- Ian Turton +1
- Justin Deoliveira
- Jody Garnett +1
- Simone Giannecchini +0
Tasks
-
:white_check_mark: Implement single layer z-order control
-
Implement cross layer z-ordering control
-
Documentation changes for [gt-renderer|http://docs.geotools.org/latest/userguide/library/render/style.html#featuretypestyle] docs for FeatureTypeStyle.
- :white_check_mark: document sortBy
- document sortByGroup