Geotools Sld TextSymbolizer 将文本绘制到错误的位置
Geotools Sld TextSymbolizer drawes text to wrong places
我使用 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 有效,我在所需位置得到了一个点,但文本符号生成了成百上千的标签:
在此示例输出中,“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,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;
}
我用的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 的值,我得到了这个图块:
更新
在进一步调查中,正如您评论的那样,如果您从单个渲染器连续绘制两个图块,我很确定您应该能够做到这一点,就会出现问题。所以它看起来像是渲染器中的一个错误,当它开始绘制一个新区域时没有清除标签缓存。随意 post 关于 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
方法中,似乎对我有用。
我使用 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 有效,我在所需位置得到了一个点,但文本符号生成了成百上千的标签:
在此示例输出中,“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,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;
}
我用的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 的值,我得到了这个图块:
更新
在进一步调查中,正如您评论的那样,如果您从单个渲染器连续绘制两个图块,我很确定您应该能够做到这一点,就会出现问题。所以它看起来像是渲染器中的一个错误,当它开始绘制一个新区域时没有清除标签缓存。随意 post 关于 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
方法中,似乎对我有用。