MS RFC 79: Layer Masking¶
- Date:
2011/11/30
- Author:
Thomas Bonfort
- Author:
Alan Boudreault
- Contact:
tbonfort at terriscope.fr
- Contact:
aboudreault at mapgears.com
- Last Edited:
2012/05/11
- Status:
Implemented
- Version:
MapServer 6.2
1. Overview¶
For some applications, it is desirable to mask out one or more layers so that only the features that intersect another set of features are rendered in the returned image. While it is relatively trivial to achieve this goal with sql joins if all the data is stored in postgis, the task becomes much more evolved or even impossible if the layer to be masked, or the layer to use as a mask, comes from a shapefile or a raster datasource.
A example use-case for this could be rendering meteo data for a given client, but only on the areas where the client has purchased the service. In this case, the meteo data should only be rendered on the areas covered by a set of polygons that represent the purchased areas.
Another example use-case given an input DEM raster, could be to only render data where the elevation is comprised in a given range.
To achieve these goals, the present RFC proposes the introduction of „MASK“ layers, where only the features that intersect the given mask are rendered onto the final image.
2. Proposed solution¶
In order to work with all layer types, this RFC proposes to implement layer masking at the pixel level, with the addition of a single „MASK“ mapfile keyword. The MASK keywords is placed at the layer level, and takes a single argument which is the name of another mapfile layer that should be used as a mask.
The mechanism to achieve masking at rendering time is:
Each layer that is used as a mask is rendered in its own temporary image. All the filtering and styling is done as usual, which implies that raster classification/filtering can be performed, as well as style-level geomtransforms, etc…
When a layer references a MASK, it is rendered in its own temporary image, in the same way that is done when OPACITY is set. As before, all kind of filtering/transformations can be performed on the masked layer
The masked layer is blended onto the final map image, but only for the pixels that have been set in the mask image.
Example of MASK usage:
LAYER
NAME "parcels"
TYPE POLYGON
STATUS OFF
DATA "the_geom from parcels where clientid='%token%'"
CLASS
STYLE
COLOR 0 0 0
END
END
END
LAYER
NAME "meteo"
STATUS ON
TYPE RASTER
DATA "raster.tif"
MASK "parcels"
END
Bemerkung
The layer used as a mask will be rendered in the final map image if its status is set to DEFAULT, or if its status is set to ON and the layer name is included in the requested layers. Most users of this feature will probably want to set the mask layer to STATUS OFF.
3. Implementation Details¶
The parser will have a MASK keyword added to it, expecting a string
The layerObj will have two properties added to it:
char* masklayer : the name of the layer that should be used as a mask
imageObj* maskimage : for a layer that has been referenced as a mask by another layer, this will contain the pixels that should be used to determine where data can be rendered on the final map image. This is to prevent the mask layer from being rendered multiple times if it is referenced by multiple layers.
In msDrawLayer(), if the current layer references a MASK layer:
a temp image is created and rendered into by another call to msDrawLayer()
a temp image is created and rendered into following the same codepath as if layer->opacity has been set
the second temporary image’s alpha channel is tampered with and set to 0 for all the pixels that haven’t been set in the first temporary image.
the second temporary image is blended into the final map image, again following codepath as if layer->opacity has been set.
3.1 Files affected¶
The following files will be modified/created by this RFC:
mapserver.h/mapfile.c/mapfile.h/maplexer.l/mapcopy.c: parser and new layerObj members
mapdraw.c: implementation in msDrawLayer()
maplabel.c: implementation for dropping labels from the labelcache if they don't
intersect the mask layer in msAddLabel()
3.2 MapScript¶
Getters and Setters will have to be added to programmatically add a MASK to a layerObj. No other issues are to be expected.
3.4 Backwards Compatibility Issues¶
This change provides a new functionality with no backwards compatibility issues being considered.
4. Limitations¶
Querying: The masking is done at the pixel level, as such all operations that query the datasource will not mask out features.
Vector renderers: masking operations will not be supported on the vector renderers (svg,pdf)
Labelling: Labelling is usually performed after all layers have been rendered, in the labelcache phase.
For layers that are referenced as a mask, the layer’s labelcache will be set to OFF, which results in all labels being directly added to the temporary mask image instead of being added to the labelcache. Of course, it is not recommended to add labels to a layer used as a mask (that is, unless someone finds a compelling use-case to do so)
If the layer that is being masked has labels, these have the potential to be rendered outside the mask area if they go through the labelcache. To overcome this, there will be a test to ensure that the labelpoint is contained in the masked areas before adding it to the labelcache. Note that the label text itself might be rendered outside of the masked areas, but this should not be an issue. If the masked layer is set with LABELCACHE FALSE, the rendered labels will be filtered out automatically at the pixel level, although there will probably be truncated labels that can appear.
5. Error Handling¶
There are no special cases to treat here aside from the classic ones (parsing errors, invalid layer referenced by MASK, invalid renderer selected)
6. Bug ID¶
7. Voting history¶
Passed with +1 from ThomasB, MichaelS, StephenW, AssefaY, FrankW, TamasS, DanielM, JeffMcK, TomK, OlivierC, SteveL