Skip to content

Commit 884096e

Browse files
dromagnoliaaime
authored andcommitted
GWC improvements:better resolution choice and Support HintsLevel
1 parent af66a11 commit 884096e

9 files changed

Lines changed: 254 additions & 116 deletions

File tree

documentation/en/user/source/services/wms.rst

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,31 @@ Note that to use WMS you should not have two grid sets with the same SRS defined
3939
Support for Regular WMS Clients
4040
-------------------------------
4141

42-
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.
43-
42+
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.
4443
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.
44+
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.
45+
46+
47+
Configuring interpolation for WMS Layer via HintsLevel
48+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
49+
When GeoWebCache recombines tiles into a single image, the resulting raster is rescaled to match the requested output size.
50+
The interpolation method used during this rescaling step can be controlled by setting an optional configuration parameter ``hintsLevel``.
51+
It can be specified on WMS layers in ``geowebcache.xml``:
52+
53+
::
54+
55+
<wmsLayer>
56+
...
57+
<hintsLevel>QUALITY</hintsLevel>
58+
...
59+
</wmsLayer>
60+
61+
Valid values are:
62+
63+
- ``SPEED`` — uses nearest-neighbor interpolation (fastest, lowest quality)
64+
- ``DEFAULT`` — uses bilinear interpolation (balanced)
65+
- ``QUALITY`` — uses bicubic interpolation (highest quality, slowest)
66+
67+
If not specified, the default behavior is equivalent to ``DEFAULT``.
68+
69+
This setting affects the final scaling step when GeoWebCache assembles tiles into a single image for non-tiled WMS requests.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
3+
* Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
4+
* later version.
5+
*
6+
* <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
7+
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8+
*
9+
* <p>You should have received a copy of the GNU Lesser General Public License along with this program. If not, see
10+
* <http://www.gnu.org/licenses/>.
11+
*
12+
* <p>Copyright 2026
13+
*/
14+
package org.geowebcache.config;
15+
16+
import java.awt.RenderingHints;
17+
import java.util.Map;
18+
19+
/** Enum storing the Hints associated to one of the 3 configurations(SPEED, QUALITY, DEFAULT) */
20+
public enum HintsLevel {
21+
QUALITY(0, "quality"),
22+
DEFAULT(1, "default"),
23+
SPEED(2, "speed");
24+
25+
@SuppressWarnings("ImmutableEnumChecker") // RenderingHints is mutable
26+
private final RenderingHints hints;
27+
28+
private final String mode;
29+
30+
HintsLevel(int numHint, String mode) {
31+
this.mode = mode;
32+
switch (numHint) {
33+
// QUALITY HINTS
34+
case 0:
35+
hints = new RenderingHints(
36+
RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
37+
hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
38+
hints.add(new RenderingHints(
39+
RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON));
40+
hints.add(new RenderingHints(
41+
RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY));
42+
hints.add(new RenderingHints(
43+
RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC));
44+
hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
45+
hints.add(new RenderingHints(
46+
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON));
47+
hints.add(new RenderingHints(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE));
48+
break;
49+
// DEFAULT HINTS
50+
case 1:
51+
hints = new RenderingHints(
52+
RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_DEFAULT);
53+
hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT));
54+
hints.add(new RenderingHints(
55+
RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT));
56+
hints.add(new RenderingHints(
57+
RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT));
58+
hints.add(new RenderingHints(
59+
RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR));
60+
hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_DEFAULT));
61+
hints.add(new RenderingHints(
62+
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT));
63+
hints.add(new RenderingHints(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT));
64+
break;
65+
// SPEED HINTS
66+
case 2:
67+
hints = new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
68+
hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF));
69+
hints.add(new RenderingHints(
70+
RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF));
71+
hints.add(new RenderingHints(
72+
RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED));
73+
hints.add(new RenderingHints(
74+
RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR));
75+
hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED));
76+
hints.add(new RenderingHints(
77+
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
78+
hints.add(new RenderingHints(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE));
79+
break;
80+
default:
81+
hints = null;
82+
}
83+
}
84+
85+
public RenderingHints getRenderingHints() {
86+
if (hints == null) {
87+
return null;
88+
}
89+
@SuppressWarnings("unchecked")
90+
RenderingHints copy = new RenderingHints((Map) hints);
91+
return copy;
92+
}
93+
94+
public String getModeName() {
95+
return mode;
96+
}
97+
98+
public static HintsLevel getHintsForMode(String mode) {
99+
100+
if (mode != null) {
101+
if (mode.equalsIgnoreCase(QUALITY.getModeName())) {
102+
return QUALITY;
103+
} else if (mode.equalsIgnoreCase(SPEED.getModeName())) {
104+
return SPEED;
105+
} else {
106+
return DEFAULT;
107+
}
108+
} else {
109+
return DEFAULT;
110+
}
111+
}
112+
}

geowebcache/core/src/main/java/org/geowebcache/layer/TileLayer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import javax.annotation.Nullable;
3030
import org.geotools.util.logging.Logging;
3131
import org.geowebcache.GeoWebCacheException;
32+
import org.geowebcache.config.HintsLevel;
3233
import org.geowebcache.config.Info;
3334
import org.geowebcache.conveyor.ConveyorTile;
3435
import org.geowebcache.filter.parameters.ParameterFilter;
@@ -187,6 +188,12 @@ public Map<String, org.geowebcache.config.legends.LegendInfo> getLayerLegendsInf
187188

188189
public abstract boolean isQueryable();
189190

191+
/** @return the hints level to use when rendering this layer */
192+
public HintsLevel getHintsLevel() {
193+
// default implementation, subclasses can override to provide a different hints level
194+
return HintsLevel.DEFAULT;
195+
}
196+
190197
/**
191198
* The timeout used when querying the backend server. The same value is used for both the connection and the data
192199
* timeout, so in theory the timeout could be twice this value.

geowebcache/core/src/main/java/org/geowebcache/layer/wms/WMSLayer.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.hc.core5.http.HttpEntity;
2929
import org.geotools.util.logging.Logging;
3030
import org.geowebcache.GeoWebCacheException;
31+
import org.geowebcache.config.HintsLevel;
3132
import org.geowebcache.config.XMLGridSubset;
3233
import org.geowebcache.config.legends.LegendsRawInfo;
3334
import org.geowebcache.conveyor.Conveyor.CacheResult;
@@ -123,6 +124,8 @@ public enum HttpRequestMode {
123124

124125
private HttpRequestMode httpRequestMode = HttpRequestMode.Get;
125126

127+
private HintsLevel hintsLevel;
128+
126129
WMSLayer() {
127130
// default constructor for XStream
128131
}
@@ -679,6 +682,15 @@ public String getProxyUrl() {
679682
return proxyUrl;
680683
}
681684

685+
@Override
686+
public HintsLevel getHintsLevel() {
687+
return hintsLevel != null ? hintsLevel : HintsLevel.DEFAULT;
688+
}
689+
690+
public void setHintsLevel(HintsLevel hintsLevel) {
691+
this.hintsLevel = hintsLevel;
692+
}
693+
682694
/** Mandatory */
683695
public void setSourceHelper(WMSSourceHelper source) {
684696
log.fine("Setting sourceHelper on " + this.name);

geowebcache/core/src/main/resources/org/geowebcache/config/geowebcache.xsd

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,13 @@
568568
</xs:element>
569569
</xs:sequence>
570570
</xs:complexType>
571-
571+
<xs:simpleType name="HintsLevel">
572+
<xs:restriction base="xs:string">
573+
<xs:enumeration value="SPEED"/>
574+
<xs:enumeration value="DEFAULT"/>
575+
<xs:enumeration value="QUALITY"/>
576+
</xs:restriction>
577+
</xs:simpleType>
572578
<xs:complexType name="WmsLayer">
573579
<xs:complexContent>
574580
<xs:extension base="gwc:AbstractTileLayer">
@@ -606,6 +612,15 @@
606612
</xs:documentation>
607613
</xs:annotation>
608614
</xs:element>
615+
<xs:element name="hintsLevel" type="gwc:HintsLevel" minOccurs="0">
616+
<xs:annotation>
617+
<xs:documentation xml:lang="en">
618+
Rendering hints level used when recombining tiles for full WMS responses.
619+
SPEED uses nearest-neighbor interpolation, DEFAULT uses bilinear interpolation,
620+
and QUALITY uses bicubic interpolation.
621+
</xs:documentation>
622+
</xs:annotation>
623+
</xs:element>
609624
<xs:element name="gutter" type="xs:integer" minOccurs="0">
610625
<xs:annotation>
611626
<xs:documentation xml:lang="en">

geowebcache/wms/src/main/java/org/geowebcache/service/wms/BufferedImageWrapper.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.awt.Graphics2D;
1818
import java.awt.RenderingHints;
1919
import java.awt.image.BufferedImage;
20+
import org.geowebcache.config.HintsLevel;
2021

2122
class BufferedImageWrapper {
2223
/** Mosaic image */
@@ -57,7 +58,7 @@ public BufferedImage getCanvas() {
5758
}
5859

5960
// Hints settings
60-
RenderingHints hintsTemp = WMSTileFuser.HintsLevel.DEFAULT.getRenderingHints();
61+
RenderingHints hintsTemp = HintsLevel.DEFAULT.getRenderingHints();
6162

6263
if (hints != null) {
6364
hintsTemp = hints;

geowebcache/wms/src/main/java/org/geowebcache/service/wms/WMSService.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.geowebcache.GeoWebCacheException;
3535
import org.geowebcache.GeoWebCacheExtensions;
3636
import org.geowebcache.config.BaseConfiguration;
37+
import org.geowebcache.config.HintsLevel;
3738
import org.geowebcache.config.ServerConfiguration;
3839
import org.geowebcache.config.TileLayerConfiguration;
3940
import org.geowebcache.conveyor.Conveyor;
@@ -290,6 +291,13 @@ public void handleRequest(Conveyor conv) throws GeoWebCacheException {
290291
} else if (tile.getHint().equalsIgnoreCase("getmap")) {
291292
getSecurityDispatcher().checkSecurity(tile);
292293
WMSTileFuser wmsFuser = getFuser(tile.servletReq);
294+
TileLayer tileLayer = tile.getLayer();
295+
if (tileLayer != null) {
296+
HintsLevel hintsLevel = tileLayer.getHintsLevel();
297+
if (hintsLevel != null) {
298+
wmsFuser.setHintsConfiguration(hintsLevel.getModeName());
299+
}
300+
}
293301
try {
294302
wmsFuser.writeResponse(tile.servletResp, stats);
295303
} catch (SecurityException e) {

0 commit comments

Comments
 (0)