Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions documentation/en/user/source/services/wms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,31 @@ Note that to use WMS you should not have two grid sets with the same SRS defined
Support for Regular WMS Clients
-------------------------------

GeoWebCache can recombine and resample tiles to answer arbitrary WMS requests. To enable this feature, open ``geowebcache-wmsservice-context.xml``, find ``<property name="fullWMS"><value>FALSE</value></property>`` and change to ``<property name="fullWMS"><value>TRUE</value></property>``. Another way to enable this feature is to add the following string to the ``geowebcache.xml`` file: ``<fullWMS>TRUE</fullWMS>``. All layers that are to support this feature must currently be configured to support a PNG format. Inside the WMS request the user can add a new WMS parameter called **hints** which can be set to one of the following configurations: *speed*, *default*, *quality*. Going from *speed* to *quality* the image quality is increased but also the computation time.

GeoWebCache can recombine and resample tiles to answer arbitrary WMS requests. To enable this feature, open ``geowebcache-wmsservice-context.xml``, find ``<property name="fullWMS"><value>FALSE</value></property>`` and change to ``<property name="fullWMS"><value>TRUE</value></property>``. Another way to enable this feature is to add the following string to the ``geowebcache.xml`` file: ``<fullWMS>TRUE</fullWMS>``. All layers that are to support this feature must currently be configured to support a PNG format. Inside the WMS request the user can add a new WMS parameter called **hints** which can be set to one of the following configurations: *speed*, *default*, *quality*. Going from *speed* to *quality* the image quality is increased but also the computation time.
Note that this requires GeoWebCache to decompress many tiles and recompress the resulting canvas; also for PNG8 and GIF output formats an optimal palette is calculated. Response times will therefore be on the order of seconds, depending on the size of the requested image and the tile sizes. You may have to increase the heap size of the Java process (``-Xmx256M``) to use this functionality.
The resolution used for tile recomposition is selected as the closest available match from the underlying grid set to the requested resolution. If an exact match is not available, the nearest resolution level is chosen based on proximity, and the resulting image is rescaled to the requested output size.


Configuring interpolation for WMS Layer via HintsLevel
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When GeoWebCache recombines tiles into a single image, the resulting raster is rescaled to match the requested output size.
The interpolation method used during this rescaling step can be controlled by setting an optional configuration parameter ``hintsLevel``.
It can be specified on WMS layers in ``geowebcache.xml``:

::

<wmsLayer>
...
<hintsLevel>QUALITY</hintsLevel>
...
</wmsLayer>

Valid values are:

- ``SPEED`` — uses nearest-neighbor interpolation (fastest, lowest quality)
- ``DEFAULT`` — uses bilinear interpolation (balanced)
- ``QUALITY`` — uses bicubic interpolation (highest quality, slowest)

If not specified, the default behavior is equivalent to ``DEFAULT``.

This setting affects the final scaling step when GeoWebCache assembles tiles into a single image for non-tiled WMS requests.
112 changes: 112 additions & 0 deletions geowebcache/core/src/main/java/org/geowebcache/config/HintsLevel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* <p>You should have received a copy of the GNU Lesser General Public License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*
* <p>Copyright 2026
*/
package org.geowebcache.config;

import java.awt.RenderingHints;
import java.util.Map;

/** Enum storing the Hints associated to one of the 3 configurations(SPEED, QUALITY, DEFAULT) */
public enum HintsLevel {
QUALITY(0, "quality"),
DEFAULT(1, "default"),
SPEED(2, "speed");

@SuppressWarnings("ImmutableEnumChecker") // RenderingHints is mutable
private final RenderingHints hints;

private final String mode;

HintsLevel(int numHint, String mode) {
this.mode = mode;
switch (numHint) {
// QUALITY HINTS
case 0:
hints = new RenderingHints(
RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
hints.add(new RenderingHints(
RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON));
hints.add(new RenderingHints(
RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY));
hints.add(new RenderingHints(
RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC));
hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
hints.add(new RenderingHints(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON));
hints.add(new RenderingHints(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE));
break;
// DEFAULT HINTS
case 1:
hints = new RenderingHints(
RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_DEFAULT);
hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT));
hints.add(new RenderingHints(
RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT));
hints.add(new RenderingHints(
RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT));
hints.add(new RenderingHints(
RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR));
hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_DEFAULT));
hints.add(new RenderingHints(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT));
hints.add(new RenderingHints(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT));
break;
// SPEED HINTS
case 2:
hints = new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF));
hints.add(new RenderingHints(
RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF));
hints.add(new RenderingHints(
RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED));
hints.add(new RenderingHints(
RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR));
hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED));
hints.add(new RenderingHints(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
hints.add(new RenderingHints(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE));
break;
default:
hints = null;
}
}

public RenderingHints getRenderingHints() {
if (hints == null) {
return null;
}
@SuppressWarnings("unchecked")
RenderingHints copy = new RenderingHints((Map) hints);
return copy;
}

public String getModeName() {
return mode;
}

public static HintsLevel getHintsForMode(String mode) {

if (mode != null) {
if (mode.equalsIgnoreCase(QUALITY.getModeName())) {
return QUALITY;
} else if (mode.equalsIgnoreCase(SPEED.getModeName())) {
return SPEED;
} else {
return DEFAULT;
}
} else {
return DEFAULT;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import javax.annotation.Nullable;
import org.geotools.util.logging.Logging;
import org.geowebcache.GeoWebCacheException;
import org.geowebcache.config.HintsLevel;
import org.geowebcache.config.Info;
import org.geowebcache.conveyor.ConveyorTile;
import org.geowebcache.filter.parameters.ParameterFilter;
Expand Down Expand Up @@ -187,6 +188,12 @@ public Map<String, org.geowebcache.config.legends.LegendInfo> getLayerLegendsInf

public abstract boolean isQueryable();

/** @return the hints level to use when rendering this layer */
public HintsLevel getHintsLevel() {
// default implementation, subclasses can override to provide a different hints level
return HintsLevel.DEFAULT;
}

/**
* The timeout used when querying the backend server. The same value is used for both the connection and the data
* timeout, so in theory the timeout could be twice this value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.hc.core5.http.HttpEntity;
import org.geotools.util.logging.Logging;
import org.geowebcache.GeoWebCacheException;
import org.geowebcache.config.HintsLevel;
import org.geowebcache.config.XMLGridSubset;
import org.geowebcache.config.legends.LegendsRawInfo;
import org.geowebcache.conveyor.Conveyor.CacheResult;
Expand Down Expand Up @@ -123,6 +124,8 @@ public enum HttpRequestMode {

private HttpRequestMode httpRequestMode = HttpRequestMode.Get;

private HintsLevel hintsLevel;

WMSLayer() {
// default constructor for XStream
}
Expand Down Expand Up @@ -679,6 +682,15 @@ public String getProxyUrl() {
return proxyUrl;
}

@Override
public HintsLevel getHintsLevel() {
return hintsLevel != null ? hintsLevel : HintsLevel.DEFAULT;
}

public void setHintsLevel(HintsLevel hintsLevel) {
this.hintsLevel = hintsLevel;
}

/** Mandatory */
public void setSourceHelper(WMSSourceHelper source) {
log.fine("Setting sourceHelper on " + this.name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,13 @@
</xs:element>
</xs:sequence>
</xs:complexType>

<xs:simpleType name="HintsLevel">
<xs:restriction base="xs:string">
<xs:enumeration value="SPEED"/>
<xs:enumeration value="DEFAULT"/>
<xs:enumeration value="QUALITY"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="WmsLayer">
<xs:complexContent>
<xs:extension base="gwc:AbstractTileLayer">
Expand Down Expand Up @@ -606,6 +612,15 @@
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="hintsLevel" type="gwc:HintsLevel" minOccurs="0">
<xs:annotation>
<xs:documentation xml:lang="en">
Rendering hints level used when recombining tiles for full WMS responses.
SPEED uses nearest-neighbor interpolation, DEFAULT uses bilinear interpolation,
and QUALITY uses bicubic interpolation.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="gutter" type="xs:integer" minOccurs="0">
<xs:annotation>
<xs:documentation xml:lang="en">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import org.geowebcache.config.HintsLevel;

class BufferedImageWrapper {
/** Mosaic image */
Expand Down Expand Up @@ -57,7 +58,7 @@ public BufferedImage getCanvas() {
}

// Hints settings
RenderingHints hintsTemp = WMSTileFuser.HintsLevel.DEFAULT.getRenderingHints();
RenderingHints hintsTemp = HintsLevel.DEFAULT.getRenderingHints();

if (hints != null) {
hintsTemp = hints;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.geowebcache.GeoWebCacheException;
import org.geowebcache.GeoWebCacheExtensions;
import org.geowebcache.config.BaseConfiguration;
import org.geowebcache.config.HintsLevel;
import org.geowebcache.config.ServerConfiguration;
import org.geowebcache.config.TileLayerConfiguration;
import org.geowebcache.conveyor.Conveyor;
Expand Down Expand Up @@ -290,6 +291,13 @@ public void handleRequest(Conveyor conv) throws GeoWebCacheException {
} else if (tile.getHint().equalsIgnoreCase("getmap")) {
getSecurityDispatcher().checkSecurity(tile);
WMSTileFuser wmsFuser = getFuser(tile.servletReq);
TileLayer tileLayer = tile.getLayer();
if (tileLayer != null) {
HintsLevel hintsLevel = tileLayer.getHintsLevel();
if (hintsLevel != null) {
wmsFuser.setHintsConfiguration(hintsLevel.getModeName());
}
}
try {
wmsFuser.writeResponse(tile.servletResp, stats);
} catch (SecurityException e) {
Expand Down
Loading
Loading