GeoServer WMS SLD scale rules causing boundaries to disappear near globe horizon in Cesium

Hello everyone,

I am using GeoServer WMS layers in Cesium (Cesium for Unreal Engine style globe rendering) to display political boundaries on a 3D globe.

Currently I have two boundary datasets:

  • Country boundaries

  • State boundaries

Both are served from GeoServer as WMS and consumed in Cesium using a single WebMapServiceRasterOverlay.

Goal

I want:

  • Country borders visible at large zoom level

  • State borders visible at low zoom level

So I attempted to use SLD scale rules with MaxScaleDenominator.

Example idea:

  • Countries: visible at larger scales (zoomed out)

  • States: visible only at smaller scales (zoomed in)

Problem

When using MaxScaleDenominator in the SLD for the state layer, I observe that borders disappear around the horizon of the globe.

Near the center of the screen, borders appear correctly, but near the edges / horizon of the globe the borders are missing.

This seems to happen because GeoServer calculates scale based on the tile BBOX, while Cesium renders a 3D globe where the same tile can represent vastly different scales depending on distance from the camera.

As a result, the scale used for rule evaluation sometimes exceeds the MaxScaleDenominator, and GeoServer stops rendering the borders for that tile entirely.

Constraints

There are a few constraints I cannot change:

  1. I must use only a single Cesium WMS raster overlay (cannot create multiple overlays).

  2. I want countries and states to appear at different zoom levels.

  3. I cannot refresh or recreate the WMS overlay dynamically because it causes visible flickering and looks bad.

  4. Stroke width cannot be less than 1 (so hiding with extremely thin strokes is not reliable).

  5. Borders should remain visible across the entire globe including near the horizon.

Current Approach

Using SLD rules like:

<?xml version="1.0" encoding="UTF-8"?>

<sld:StyledLayerDescriptor xmlns:sld="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" version="1.0.0">
  <sld:NamedLayer>
    <sld:Name>StateBoundary</sld:Name>
    <sld:UserStyle>
      <sld:FeatureTypeStyle>
        <sld:Rule>
          <sld:MaxScaleDenominator>3.0E7</sld:MaxScaleDenominator>

          <sld:LineSymbolizer>
            <sld:Stroke>
              <sld:CssParameter name="stroke">#FFFFFF</sld:CssParameter>

              <sld:CssParameter name="stroke-opacity">1.0</sld:CssParameter>

              <sld:CssParameter name="stroke-width">1.0</sld:CssParameter>

              <sld:CssParameter name="stroke-dasharray">6.0 3.0</sld:CssParameter>
          
        </sld:Stroke>
      </sld:LineSymbolizer>
      
    </sld:Rule>
  </sld:FeatureTypeStyle>
</sld:UserStyle>
  </sld:NamedLayer>
</sld:StyledLayerDescriptor>

This works near the camera but fails near the horizon.

Any suggestions or examples from similar setups would be greatly appreciated.

Thank you!

Hey @Mayank_i,

I am curious as to why you cannot have multiple WMS raster overlays?

Based on your question it sounds like you are hosting your own WMS, I’m wondering if you can update your WMS SLD rules to switch datasets based on a query paramter, let’s call it “client_scale”.
Client scale could be distance from ground, distance from camera to earth at center of view. whatever feels right. You would need to compute “client_scale” on a regular basis within Unreal Engine, and then update the WebMapServiceRasterOverlay query to include the updated “client_scale”. This shouldn’t be too heavy computationally to do every frame, but you could do it at some other interval if needed.

Let us know how that goes, best of luck!

Thanks for the suggestion. @darcyvdd

The reason I cannot use multiple WMS raster overlays is that Cesium for Unreal currently recommends using a maximum of three raster overlays for performance reasons. I could technically add more, but it is not recommended and may negatively impact performance.

At the moment, I am already using all three overlays:

  1. Sentinel imagery

  2. Boundaries overlay (this is the one I am trying to improve)

  3. Night tiles

Regarding the suggestion of adding a client_scale query parameter and updating it dynamically: my concern is that changing the URL of the WebMapServiceRasterOverlay forces Cesium to refresh the entire globe. When this happens, the globe briefly falls back to low-resolution tiles and then reloads the high-resolution tiles, which creates a noticeable visual refresh.

Because of this behavior, updating the overlay URL frequently (for example, every frame or even at intervals) may lead to visible tile reloading and degraded user experience.

If there is a way to update the WMS parameters without forcing a full globe refresh, or another approach that works better with Cesium’s raster overlay system, I would be very interested to hear about it.

I have opened a feature request herefor the potential to update query params without causing a fell visual refresh, this is no guarantee that this will happen but it does sound useful.

In the mean time, are you using all your overlays at once? Or are some disabled at times?

It could be worth creating another overlay, and having one for Country Borders and one for State Borders, and switch the visibility/active of the two from within Unreal. This may cause a visual refresh also, which if it does you could try having both active, but set the opacity to 0 of one, and 1 on the other, and switch them based on some condition within Unreal. This may have some performance overhead, but could be worth trying.