Skip to Content
AdvancedLayer Masks

MapsGL Android - Layer Masks

Layer masks allow you to control the visibility of different parts of a layer by using another layer as a mask. This is useful for creating complex visual effects, such as revealing or hiding parts of a layer based on the shape or content of another layer. By using administrative layers as masks, you can create more dynamic and context-aware maps that highlight specific regions or features.

When configuring a mask for a MapsGL layer, the layer being used as the mask must have already been added to the map. You can then specify the mask layer in the layer’s configuration options.

Configuration

The following options are available when configuring a mask for a layer (Kotlin LayerMaskSpec, set on layer.layerMask or synthesized from maskLayerIds):

// Equivalent to JS LayerMaskSpecification: // // layerIds → maskLayerIds = listOf("admin-mask") // or LayerMaskSpec(layers = listOf(LayerMaskLayerConfig(id = "admin-mask"))) // invert → LayerMaskSpec.invert // mode → LayerMaskSpec.mode (StencilMaskCombineMode.ALL | ANY)
OptionDescriptionDefault
layerIdsType: List<String> (maskLayerIds)Layer identifiers of the layers to use for masking this layer’s output. Use in combination with mode to control how masks are applied. Set as layer.maskLayerIds or map each id to LayerMaskLayerConfig(id = …) in layer.layerMask.emptyList()
invertType: Boolean? ()Whether to invert the mask, meaning the layer will be visible outside of the mask area.null
modeType: StencilMaskCombineMode (JS 'all' / 'any')ALL — visible only if all masks apply (intersection). ANY — visible if any mask applies (union).ALL

Example usage

The following example demonstrates how to mask a raster tile layer using an administrative boundary layer provided as GeoJSON as the mask:

import com.mapbox.geojson.FeatureCollection import com.xweather.mapsgl.layers.spec.FillLayerDescriptor import com.xweather.mapsgl.layers.spec.MaskLayerKind import com.xweather.mapsgl.sources.GeoJSONSource import com.xweather.mapsgl.sources.source.spec.GeoJSONSourceDescriptor import com.xweather.mapsgl.weather.WeatherService // add the GeoJSON source for the administrative boundary mapController.addSource(GeoJSONSourceDescriptor(id = "admin-boundaries")) (mapController.getSource("admin-boundaries") as GeoJSONSource).data = FeatureCollection.fromJson( """ { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": {}, "geometry": { "type": "Polygon", "coordinates": [ [ [-9.034817674180246, 41.88057058365967], [-8.67194576662672, 42.13468943945496], [-8.263856980817792, 42.28046865495034], [-8.013174607769912, 41.790886135417125], [-7.422512986673795, 41.79207469335983], [-7.251308966490824, 41.91834605566505], [-6.668605515967656, 41.883386949219584], [-6.389087693700915, 41.381815497394655], [-6.851126674822552, 41.11108266861753], [-6.864019944679385, 40.33087189387483], [-7.026413133156595, 40.184524237624245], [-7.066591559263529, 39.71189158788277], [-7.498632371439725, 39.62957103124181], [-7.098036668313128, 39.03007274022378], [-7.374092169616318, 38.37305858006492], [-7.029281175148796, 38.07576406508977], [-7.166507941099865, 37.803894354802225], [-7.537105475281024, 37.42890432387623], [-7.453725551778092, 37.09778758396607], [-7.855613165711985, 36.83826854099627], [-8.382816127953689, 36.97888011326246], [-8.898856980820327, 36.86880931248078], [-8.746101446965554, 37.65134552667661], [-8.839997524439879, 38.26624339451761], [-9.287463751655224, 38.3584858261586], [-9.526570603869715, 38.73742910415491], [-9.446988898140232, 39.39206614842837], [-9.048305223008427, 39.75509308527877], [-8.977353481471681, 40.15930613866581], [-8.768684047877102, 40.76063894303019], [-8.79085323733031, 41.18433401139126], [-8.99078935386757, 41.54345937760364], [-9.034817674180246, 41.88057058365967] ] ] } } ] } """.trimIndent(), ) // add the administrative boundary layer to use as a mask mapController.addLayer( FillLayerDescriptor( id = "admin-mask", source = "admin-boundaries", stencilOnly = true, ), beforeID = null, ) // add the raster data tile source and layer (SatelliteGeocolor) val satellite = WeatherService.SatelliteGeocolor(mapController.service) satellite.layer.id = "satellite" satellite.layer.mask = MaskLayerKind.NONE satellite.layer.maskLayerIds = listOf("admin-mask") satellite.layer.paint.opacity = 0.7f mapController.addWeatherLayer(satellite)
Applying a custom GeoJSON mask to a raster tile layer.

Masking weather layers

Weather layers in MapsGL can also utilize layer masks to control their visibility based on administrative boundaries or other geographic features. This is particularly useful for displaying weather data only within specific regions, such as countries, states, or cities. By applying masks to weather layers, you can create more focused and relevant visualizations that highlight weather conditions in targeted areas.

Basic land and water masks

MapsGL provides built-in land and water mask layers that can be used to mask weather layers based on land and water boundaries. This is useful for displaying weather data only over land or water areas.

For example, the following masks temperatures to show only over land areas:

import com.xweather.mapsgl.layers.spec.LayerMasks import com.xweather.mapsgl.weather.WeatherService val temperature = WeatherService.Temperatures(mapController.service) temperature.layer.layerMask = LayerMasks.land() mapController.addWeatherLayer(temperature)
Masking temperatures to show only over land areas.

Conversely, you can mask temperatures to show only over water areas:

val temperature = WeatherService.Temperatures(mapController.service) temperature.layer.layerMask = LayerMasks.water() mapController.addWeatherLayer(temperature)
Masking temperatures to show only over water areas.

Custom mask layers

For example, you can mask a temperature layer to show temperatures only within the boundaries of a specific country or state, enhancing the clarity and usefulness of the weather data presented on the map:

import com.xweather.mapsgl.weather.WeatherService // This example assumes the admin-boundaries source and stencil-only admin-mask fill layer // from the first example above are already on the map. val temperature = WeatherService.Temperatures(mapController.service) temperature.layer.maskLayerIds = listOf("admin-mask") mapController.addWeatherLayer(temperature)
Masking temperatures to a custom GeoJSON region.

You can also invert the mask to display weather data outside of the specified region:

import com.xweather.mapsgl.layers.spec.LayerMaskLayerConfig import com.xweather.mapsgl.layers.spec.LayerMaskSpec import com.xweather.mapsgl.weather.WeatherService val temperature = WeatherService.Temperatures(mapController.service) temperature.layer.layerMask = LayerMaskSpec( layers = listOf(LayerMaskLayerConfig(id = "admin-mask")), invert = true, ) mapController.addWeatherLayer(temperature)
Masking temperatures by inverting the mask.

If you have a layer instance returned by your map controller when calling addLayer, you can also use that layer instance’s id to specify the mask:

import com.xweather.mapsgl.layers.spec.FillLayerDescriptor import com.xweather.mapsgl.layers.spec.LayerMaskLayerConfig import com.xweather.mapsgl.layers.spec.LayerMaskSpec import com.xweather.mapsgl.weather.WeatherService // This example assumes the admin-boundaries source and stencil-only admin-mask fill layer // from the first example above are already on the map. // add the administrative boundary layer to use as a mask val adminLayer = mapController.addLayer( FillLayerDescriptor( id = "admin-mask", source = "admin-boundaries", stencilOnly = true, ), beforeID = null, ) val temperature = WeatherService.Temperatures(mapController.service) temperature.layer.layerMask = LayerMaskSpec( layers = listOf(LayerMaskLayerConfig(id = adminLayer!!.id)), invert = true, ) mapController.addWeatherLayer(temperature)

Multiple masks using modes

You can also use multiple layers as masks for another layer by specifying multiple layer IDs in the layerIds array. By default, the mode is set to 'all', meaning the target layer will be visible only if all the masks are visible (intersection of all masks). You can change the mode to 'any' to make the target layer visible if any of the masks are visible (union of all masks).

For instance, you may want to mask temperatures to just major highways in the US. For this, you’d need a polygon layer for the US boundary and a stencil-only line layer for motorways (not a visible road-motorway weather layer). You can then use both layers as masks for the temperature layer.

import com.mapbox.geojson.FeatureCollection import com.xweather.mapsgl.layers.spec.FillLayerDescriptor import com.xweather.mapsgl.layers.spec.LayerMaskLayerConfig import com.xweather.mapsgl.layers.spec.LayerMaskSpec import com.xweather.mapsgl.layers.spec.LineLayerDescriptor import com.xweather.mapsgl.layers.spec.MaskLayerKind import com.xweather.mapsgl.layers.spec.StencilMaskCombineMode import com.xweather.mapsgl.map.mapbox.MapboxMapController import com.xweather.mapsgl.sources.GeoJSONSource import com.xweather.mapsgl.sources.source.spec.GeoJSONSourceDescriptor import com.xweather.mapsgl.style.LineLayerPaint import com.xweather.mapsgl.style.StrokePaint import com.xweather.mapsgl.style.StyleValue import com.xweather.mapsgl.weather.WeatherService // US boundary polygon (demo: bundled CONUS GeoJSON; production: your GeoJSON or vector tiles) mapController.addSource(GeoJSONSourceDescriptor(id = "naturalearth")) (mapController.getSource("naturalearth") as GeoJSONSource).data = FeatureCollection.fromJson(conusGeoJsonFromAssetsOrServer) val countriesLayer = mapController.addLayer( FillLayerDescriptor( id = "countries", source = "naturalearth", stencilOnly = true, ), beforeID = null, ) // Stencil-only motorway ribbon (stroke thickness in points; used by the GLES mask builder) val roadCfg = WeatherService.RoadMotorway(mapController.service) if (!mapController.hasSource(roadCfg.source.id)) { mapController.addSource(roadCfg.source) } val motorwayPaint = (roadCfg.layer.paint as? LineLayerPaint)?.copy() ?: LineLayerPaint(stroke = StrokePaint()) motorwayPaint.stroke.thickness = StyleValue.Constant(3.0) mapController.addLayer( LineLayerDescriptor( id = "road-motorway", source = roadCfg.source.id, sourceLayer = roadCfg.layer.sourceLayer, paint = motorwayPaint, filter = roadCfg.layer.filter, stencilOnly = true, ), beforeID = null, ) // Temperature visible only where the US polygon AND motorways overlap (intersection) val temperature = WeatherService.Temperatures(mapController.service) temperature.layer.mask = MaskLayerKind.NONE temperature.layer.layerMask = LayerMaskSpec( layers = listOf( LayerMaskLayerConfig(id = countriesLayer!!.id), LayerMaskLayerConfig(id = "road-motorway"), ), mode = StencilMaskCombineMode.ALL, ) mapController.addWeatherLayer(temperature) (mapController as? MapboxMapController)?.reregisterCustomStencilMask(temperature.layer.id)
Masking temperatures to show only along major highways in the US using multiple mask layers.

Using the any mode will show temperatures anywhere either of the mask layers are visible, so you’d see temperatures over the entire US as well as along the highways globally outside of the US:

val temperature = WeatherService.Temperatures(mapController.service) temperature.layer.mask = MaskLayerKind.NONE temperature.layer.layerMask = LayerMaskSpec( layers = listOf( LayerMaskLayerConfig(id = countriesLayer!!.id), LayerMaskLayerConfig(id = "road-motorway"), ), mode = StencilMaskCombineMode.ANY, ) mapController.addWeatherLayer(temperature) (mapController as? MapboxMapController)?.reregisterCustomStencilMask(temperature.layer.id)
Masking temperatures to show anywhere in the US and any major highways globally using multiple mask layers.
© 2026 Xweather (opens in a new tab)Terms of Service (opens in a new tab)Privacy Policy (opens in a new tab)