Geotools Sld TextSymbolizer 将文本绘制到错误的位置

问题描述

我使用 geotools GTRenderer 作为 Tileserver 并有一个用于样式的 SLD 文件(取自此处 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>

PointSymbolizer 工作,我在所需位置得到一分,但文本符号生成数百个标签

Bug

在这个示例输出中,“Southend-on-Sea”是我唯一希望渲染的地方。

知道点和文本符号化器之间可能有什么不同吗?

感谢您的帮助

编辑我使用的代码

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;
}

平铺渲染方法

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,(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;
}

我使用的 Shapefile 可以在这里下载:

https://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-populated-places/

解决方法

很难确定,因为您的代码中缺少一些元素,但我从这段代码中得到了合理的结果:

   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);
    }

关键部分是我为渲染器设置视口边界的地方:

        MapViewport viewport = new MapViewport();
        viewport.setBounds(tileBounds);
        map.setViewport(viewport);
        renderer.setMapContent(map);

否则渲染器仍在使用 WGS84(图层的 CRS,假设没有设置其他任何内容),而您的边界在 EPSG:3857 中,因此整个世界都会被绘制。

对于 X=16、Y=10 和 Z=5 的值,我得到这个图块:

enter image description here

更新

在进一步调查中,当您评论时,如果您从单个渲染器连续绘制两个图块,我很确定您应该能够做到这一点,则会出现问题。所以看起来渲染器中的一个错误在开始绘制新区域时没有清除标签缓存。请随时在 JIRA 上发布错误。

目前您可以通过提供自己的 LabelCache 并在每次调用 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);
}

然后添加

cache.clear(); 

renderRasterTile 方法中,似乎对我有用。