im using geotools GTRenderer as a Tileserver and have a SLD File for styling (taken from here https://docs.geoserver.org/stable/en/user/styling/sld/cookbook/points.html#point-with-styled-label):
<StyledLayerDescriptor version="1.0.0"
xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd"
xmlns="http://www.opengis.net/sld"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamedLayer>
<Name>WorldCities</Name>
<UserStyle>
<Name>Default Styler</Name>
<FeatureTypeStyle>
<Name>name</Name>
<Rule>
<PointSymbolizer>
<Graphic>
<Mark>
<WellKnownName>circle</WellKnownName>
<Fill>
<CssParameter name="fill">#FF0000</CssParameter>
</Fill>
</Mark>
<Size>6</Size>
</Graphic>
</PointSymbolizer>
<TextSymbolizer>
<Label>
<ogc:PropertyName>nameascii</ogc:PropertyName>
</Label>
<Font>
<CssParameter name="font-family">Arial</CssParameter>
<CssParameter name="font-size">12</CssParameter>
<CssParameter name="font-style">normal</CssParameter>
<CssParameter name="font-weight">bold</CssParameter>
</Font>
<LabelPlacement>
<PointPlacement>
<AnchorPoint>
<AnchorPointX>0.5</AnchorPointX>
<AnchorPointY>0.0</AnchorPointY>
</AnchorPoint>
<Displacement>
<DisplacementX>0</DisplacementX>
<DisplacementY>5</DisplacementY>
</Displacement>
</PointPlacement>
</LabelPlacement>
<Fill>
<CssParameter name="fill">#000000</CssParameter>
</Fill>
</TextSymbolizer>
</Rule>
</FeatureTypeStyle>
</UserStyle>
</NamedLayer>
The PointSymbolizer works and I get one one point at the desired location, but the text symbolizer produces hunderds of labels:
In this sample output, the place "Southend-on-Sea" is the only one i expect to be rendered.
Any idea what might be different between the point and the textsymbolizer?
Thanks for any help
Edit the code i use:
private static Style loadStyleFromXml(String path) throws Exception {
StyleFactory factory = CommonFactoryFinder.getStyleFactory();
URL resource = new File(path).toURI().toURL();
SLDParser stylereader = new SLDParser( factory, resource);
Style styles[] = stylereader.readXML();
return styles[0];
}
private static FeatureSource<SimpleFeatureType, SimpleFeature> readShapefile(String path) throws IOException {
File file = new File(path);
Map<String, Object> filemap = new HashMap<>();
filemap.put("url", file.toURI().toURL());
DataStore dataStore = DataStoreFinder.getDataStore(filemap);
String typeName = dataStore.getTypeNames()[0];
FeatureSource<SimpleFeatureType, SimpleFeature> source = dataStore.getFeatureSource(typeName);
SimpleFeatureType schema = source.getSchema();
return source;
}
Tile render method:
public synchronized byte[] renderRasterTile(int x, int y, int z){
ReferencedEnvelope tileBounds = WebMercatorTileFactory.getExtentFromTileName(new OSMTileIdentifier(x, y, new WebMercatorZoomLevel(z), "custom"));
try {
tileBounds = tileBounds.transform(CRS.decode("EPSG:3857"),true);
} catch (Exception e) {
logger.error("Unable to transfrom coords",e);
throw new RuntimeException(e);
}
BufferedImage image = new BufferedImage(tilePixelSize.width, tilePixelSize.height, BufferedImage.TYPE_INT_RGB);
Graphics2D gr = image.createGraphics();
gr.setPaint(new Color(0,0,0, (float) 0.1));
gr.fill(tilePixelSize);
try {
renderer.paint(gr, tilePixelSize, tileBounds);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write( image, "png", baos );
baos.flush();
byte[] imageInByte = baos.toByteArray();
baos.close();
return imageInByte;
} catch (IOException e) {
logger.error("Unable to render tile",e);
throw new RuntimeException(e);
}
}
public MapContent setupMap(){
MapContent map = new MapContent();
map.setTitle("WorldMap");
FeatureSource<SimpleFeatureType, SimpleFeature> featureSource = readShapefile("cities.shp");
Style style = loadStyleFromXml("cities.sld");
Layer layer = new FeatureLayer(featureSource, style,"cities");
map.addLayer(layer);
return map;
}
The Shapefile I used can be downloaded here:
https://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-populated-places/
It's hard to be sure as there are some elements missing from your code but I get reasonable looking results from this code:
try {
StreamingRenderer renderer = new StreamingRenderer();
MapViewport viewport = new MapViewport();
viewport.setBounds(tileBounds);
map.setViewport(viewport);
renderer.setMapContent(map);
renderer.paint(gr, tilePixelSize, tileBounds);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write( image, "png", baos );
baos.flush();
byte[] imageInByte = baos.toByteArray();
baos.close();
return imageInByte;
} catch (IOException e) {
System.err.println("Unable to render tile "+e);
throw new RuntimeException(e);
}
The key part is where I set the viewport bounds for the renderer:
MapViewport viewport = new MapViewport();
viewport.setBounds(tileBounds);
map.setViewport(viewport);
renderer.setMapContent(map);
otherwise the renderer is still using WGS84 (the CRS of the layer, assuming nothing else is set) while your bounds are in EPSG:3857 so the whole world gets drawn.
For values of X=16, Y=10 and Z=5 I get this tile:
On further investigation, as you comment there is a problem if you draw two tiles in a row from a single renderer which I'm pretty sure you should be able to do. So it looks like a bug in the renderer not clearing the label cache when it starts to draw a new area. Feel free to post a bug on the JIRA.
For the time being you can work round it by providing your own LabelCache
and clearing it each time you call paint
.
GTRenderer renderer = new StreamingRenderer();
LabelCache cache = new LabelCacheImpl();
private void setup(){
Map<Object, Object> hints = new HashMap<>();
hints.put(StreamingRenderer.LABEL_CACHE_KEY, cache);
renderer.setRendererHints(hints);
}
and then add
cache.clear();
in the renderRasterTile
method, seems to work for me.