You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用FXGraphics2D与StreamingRenderer调整Canvas时渲染裁剪问题求助

GeoTools + JavaFX Canvas 放大渲染裁剪问题解决方案

问题描述

开发基于GeoTools(采用WMS+WFS实现网格功能)的地图应用时,JavaFX Canvas在窗口缩小时渲染正常,但放大窗口(Canvas同步自适应)时,地图内容被裁剪,无法显示完整的十字图形(Canvas本身已验证可正常自适应)。

复现代码

import java.awt.Color;
import java.awt.Rectangle;

import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.FeatureLayer;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.SLD;
import org.geotools.styling.Style;
import org.jfree.fx.FXGraphics2D;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class ResizingTest extends Application {

    @Override
    public void start(Stage stage) {

        Canvas canvas = new Canvas(640, 480);
        BorderPane root = new BorderPane(canvas);
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();

        // Create bindings for resizing.
        canvas.widthProperty().bind(root.widthProperty());
        canvas.heightProperty().bind(root.heightProperty());

        SimpleFeatureTypeBuilder lineFeatureTypeBuilder = new SimpleFeatureTypeBuilder();
        lineFeatureTypeBuilder.setName("LineFeatureType");
        lineFeatureTypeBuilder.setCRS(DefaultGeographicCRS.WGS84);
        lineFeatureTypeBuilder.add("the_geom", LineString.class, DefaultGeographicCRS.WGS84);
        SimpleFeatureType lineFeatureType = lineFeatureTypeBuilder.buildFeatureType();
        SimpleFeatureBuilder lineFeatureBuilder = new SimpleFeatureBuilder(lineFeatureType);

        DefaultFeatureCollection lines = new DefaultFeatureCollection();

        Coordinate[][] cs = {
                { new Coordinate(-1, 42), new Coordinate(4, 46) },
                { new Coordinate(-1, 46), new Coordinate(4, 42) }
        };

        GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
        for(Coordinate [] c : cs) {
            LineString line = geometryFactory.createLineString(c);
            lineFeatureBuilder.add(line);
            SimpleFeature feature = lineFeatureBuilder.buildFeature(null);
            lines.add(feature);
        }

        MapContent map = new MapContent();
        Style style = SLD.createLineStyle(Color.RED, 1);
        Layer layer = new FeatureLayer(lines, style);
        map.addLayer(layer);
        //map.getViewport().setBounds(new ReferencedEnvelope(-1, 4, 42, 46, DefaultGeographicCRS.WGS84));

        AnimationTimer loop = new AnimationTimer() {
            @Override
            public void handle(long now) {
                GraphicsContext g = canvas.getGraphicsContext2D();
                FXGraphics2D graphics = new FXGraphics2D(g);
                graphics.setBackground(java.awt.Color.BLUE);
                Rectangle rectangle = new Rectangle( (int) canvas.getWidth(), (int) canvas.getHeight());
                graphics.clearRect(0, 0, (int) rectangle.getWidth(), (int) rectangle.getHeight());
                graphics.drawRect(100, 100, 100, 100);
                map.getViewport().setScreenArea(rectangle); // Necessary ?
                StreamingRenderer renderer = new StreamingRenderer();
                renderer.setMapContent(map);
                renderer.paint(graphics, rectangle, map.getViewport().getBounds());
                System.out.println("ScreenArea: " + map.getViewport().getScreenArea() + " - Viewport: " + map.getViewport().getBounds());
            }
        };
        loop.start();

    }

    public static void main(String[] args) {
        launch(args);
    }

}

问题现象

放大窗口时,Canvas可正常延伸,但地图渲染内容被裁剪,十字图形仅部分显示,裁剪区域无任何渲染内容:

裁剪效果示意图

解决方案

问题根源在于每次渲染时重复创建StreamingRenderer实例,以及未正确维护视口的地理边界与屏幕区域的同步。修改如下:

  1. 将StreamingRenderer移到AnimationTimer外部初始化,避免重复创建:
// 在AnimationTimer之前创建渲染器
StreamingRenderer renderer = new StreamingRenderer();
renderer.setMapContent(map);
  1. 显式设置视口的地理边界,确保缩放时地图范围正确匹配:
    取消注释代码中视口边界设置的行,初始化时固定地理范围:
// 初始化时设置视口地理边界
ReferencedEnvelope viewBounds = new ReferencedEnvelope(-1, 4, 42, 46, DefaultGeographicCRS.WGS84);
map.getViewport().setBounds(viewBounds);
  1. 优化渲染逻辑,调整后的AnimationTimer代码:
AnimationTimer loop = new AnimationTimer() {
    @Override
    public void handle(long now) {
        GraphicsContext g = canvas.getGraphicsContext2D();
        FXGraphics2D graphics = new FXGraphics2D(g);
        graphics.setBackground(java.awt.Color.BLUE);
        Rectangle rectangle = new Rectangle( (int) canvas.getWidth(), (int) canvas.getHeight());
        graphics.clearRect(0, 0, (int) rectangle.getWidth(), (int) rectangle.getHeight());
        graphics.drawRect(100, 100, 100, 100);
        
        // 更新视口屏幕区域
        map.getViewport().setScreenArea(rectangle);
        // 渲染地图
        renderer.paint(graphics, rectangle, map.getViewport().getBounds());
        System.out.println("ScreenArea: " + map.getViewport().getScreenArea() + " - Viewport: " + map.getViewport().getBounds());
    }
};
loop.start();

核心原理:

  • StreamingRenderer是重量级对象,重复创建会导致渲染状态异常,尤其是缩放时的坐标转换逻辑出错
  • 显式设置视口地理边界,确保GeoTools在计算渲染范围时能正确映射到Canvas的全部区域,避免裁剪

内容的提问来源于stack exchange,提问作者Elyas Tigre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 04:45:29