第12章 - 地图渲染与输出

第12章 - 地图渲染与输出

12.1 渲染系统概述

12.1.1 渲染架构

┌─────────────────────────────────────────────────────────────────────┐
│                        GeoTools 渲染系统                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   MapContent                                                        │
│       │                                                             │
│       ├── Layer (FeatureLayer / GridCoverageLayer)                  │
│       │       ├── FeatureSource                                     │
│       │       └── Style                                             │
│       │                                                             │
│       └── Viewport                                                  │
│               ├── Bounds (ReferencedEnvelope)                       │
│               └── ScreenArea (Rectangle)                            │
│                                                                     │
│   GTRenderer                                                        │
│       │                                                             │
│       └── StreamingRenderer (主要实现)                              │
│               │                                                     │
│               └── paint(Graphics2D, Rectangle, ReferencedEnvelope) │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

12.2 MapContent 使用

12.2.1 创建地图内容

import org.geotools.map.MapContent;
import org.geotools.map.FeatureLayer;
import org.geotools.map.Layer;

public class MapContentExample {
    
    public static MapContent createMapContent() throws Exception {
        // 创建地图内容
        MapContent map = new MapContent();
        map.setTitle("示例地图");
        
        // 加载数据
        File shpFile = new File("data/countries.shp");
        FileDataStore store = FileDataStoreFinder.getDataStore(shpFile);
        SimpleFeatureSource source = store.getFeatureSource();
        
        // 创建样式
        Style style = SLD.createPolygonStyle(Color.BLACK, Color.CYAN, 0.5f);
        
        // 创建图层
        Layer layer = new FeatureLayer(source, style);
        layer.setTitle("国家边界");
        
        // 添加到地图
        map.addLayer(layer);
        
        return map;
    }
    
    public static void addMultipleLayers(MapContent map) throws Exception {
        // 添加多个图层
        // 图层顺序:先添加的在底层
        
        // 底图图层
        FileDataStore baseStore = FileDataStoreFinder.getDataStore(
            new File("data/land.shp"));
        Style baseStyle = SLD.createPolygonStyle(Color.GRAY, Color.LIGHT_GRAY, 1f);
        map.addLayer(new FeatureLayer(baseStore.getFeatureSource(), baseStyle));
        
        // 道路图层
        FileDataStore roadStore = FileDataStoreFinder.getDataStore(
            new File("data/roads.shp"));
        Style roadStyle = SLD.createLineStyle(Color.RED, 2);
        map.addLayer(new FeatureLayer(roadStore.getFeatureSource(), roadStyle));
        
        // 点位图层
        FileDataStore poiStore = FileDataStoreFinder.getDataStore(
            new File("data/poi.shp"));
        Style poiStyle = SLD.createPointStyle("circle", Color.BLUE, Color.BLACK, 0.5f, 8);
        map.addLayer(new FeatureLayer(poiStore.getFeatureSource(), poiStyle));
    }
}

12.2.2 图层管理

public class LayerManagement {
    
    public static void manageLayers(MapContent map) {
        // 获取所有图层
        List<Layer> layers = map.layers();
        System.out.println("图层数量: " + layers.size());
        
        for (Layer layer : layers) {
            System.out.println("  - " + layer.getTitle());
            System.out.println("    可见: " + layer.isVisible());
            System.out.println("    边界: " + layer.getBounds());
        }
        
        // 图层可见性控制
        Layer layer = layers.get(0);
        layer.setVisible(false);  // 隐藏
        layer.setVisible(true);   // 显示
        
        // 图层顺序
        // 移动图层到底部
        map.moveLayer(0, layers.size() - 1);
        
        // 移动图层到顶部
        map.moveLayer(layers.size() - 1, 0);
        
        // 移除图层
        map.removeLayer(layer);
        
        // 清除所有图层
        // map.layers().clear();
    }
}

12.3 使用 StreamingRenderer

12.3.1 渲染到 BufferedImage

import org.geotools.renderer.GTRenderer;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.geometry.jts.ReferencedEnvelope;

import java.awt.image.BufferedImage;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;

public class MapRenderer {
    
    public static BufferedImage renderMap(MapContent map, int width, int height) {
        // 创建渲染器
        GTRenderer renderer = new StreamingRenderer();
        renderer.setMapContent(map);
        
        // 设置渲染提示
        RenderingHints hints = new RenderingHints(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON
        );
        hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, 
            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        renderer.setJava2DHints(hints);
        
        // 获取地图范围
        ReferencedEnvelope mapBounds = map.getMaxBounds();
        
        // 创建图像
        BufferedImage image = new BufferedImage(width, height, 
            BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = image.createGraphics();
        
        // 设置背景
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, width, height);
        
        // 渲染
        Rectangle screenArea = new Rectangle(width, height);
        renderer.paint(graphics, screenArea, mapBounds);
        
        graphics.dispose();
        
        return image;
    }
    
    // 渲染指定范围
    public static BufferedImage renderExtent(MapContent map, 
            ReferencedEnvelope extent, int width, int height) {
        
        GTRenderer renderer = new StreamingRenderer();
        renderer.setMapContent(map);
        
        BufferedImage image = new BufferedImage(width, height, 
            BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = image.createGraphics();
        
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, width, height);
        
        renderer.paint(graphics, new Rectangle(width, height), extent);
        
        graphics.dispose();
        return image;
    }
}

12.3.2 保存为图片文件

import javax.imageio.ImageIO;

public class MapExporter {
    
    // 保存为 PNG
    public static void saveToPNG(MapContent map, String outputPath, 
            int width, int height) throws Exception {
        
        BufferedImage image = MapRenderer.renderMap(map, width, height);
        ImageIO.write(image, "PNG", new File(outputPath));
        System.out.println("保存成功: " + outputPath);
    }
    
    // 保存为 JPEG
    public static void saveToJPEG(MapContent map, String outputPath, 
            int width, int height, float quality) throws Exception {
        
        BufferedImage image = MapRenderer.renderMap(map, width, height);
        
        // 转换为 RGB(JPEG 不支持透明)
        BufferedImage rgbImage = new BufferedImage(width, height, 
            BufferedImage.TYPE_INT_RGB);
        Graphics2D g = rgbImage.createGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, height);
        g.drawImage(image, 0, 0, null);
        g.dispose();
        
        // 设置 JPEG 质量
        ImageWriter writer = ImageIO.getImageWritersByFormatName("JPEG").next();
        ImageWriteParam param = writer.getDefaultWriteParam();
        param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        param.setCompressionQuality(quality);
        
        try (FileImageOutputStream out = new FileImageOutputStream(new File(outputPath))) {
            writer.setOutput(out);
            writer.write(null, new IIOImage(rgbImage, null, null), param);
        }
        
        writer.dispose();
        System.out.println("保存成功: " + outputPath);
    }
    
    // 按比例尺渲染
    public static void saveAtScale(MapContent map, String outputPath,
            Point2D center, double scale, int width, int height) throws Exception {
        
        // 计算范围
        CoordinateReferenceSystem crs = map.getCoordinateReferenceSystem();
        double groundWidth = width / scale;
        double groundHeight = height / scale;
        
        ReferencedEnvelope extent = new ReferencedEnvelope(
            center.getX() - groundWidth / 2,
            center.getX() + groundWidth / 2,
            center.getY() - groundHeight / 2,
            center.getY() + groundHeight / 2,
            crs
        );
        
        BufferedImage image = MapRenderer.renderExtent(map, extent, width, height);
        ImageIO.write(image, "PNG", new File(outputPath));
    }
}

12.4 Swing 地图显示

12.4.1 使用 JMapFrame

import org.geotools.swing.JMapFrame;

public class SwingMapDisplay {
    
    public static void showMap(MapContent map) {
        // 简单显示
        JMapFrame.showMap(map);
    }
    
    public static void showCustomMap(MapContent map) {
        // 自定义地图窗口
        JMapFrame frame = new JMapFrame(map);
        
        // 启用工具栏
        frame.enableToolBar(true);
        
        // 启用状态栏
        frame.enableStatusBar(true);
        
        // 启用图层列表
        frame.enableLayerTable(true);
        
        // 设置窗口大小
        frame.setSize(1024, 768);
        
        // 显示
        frame.setVisible(true);
    }
}

12.4.2 自定义地图面板

import org.geotools.swing.JMapPane;

public class CustomMapPanel extends JFrame {
    
    private JMapPane mapPane;
    private MapContent map;
    
    public CustomMapPanel(MapContent map) {
        this.map = map;
        initUI();
    }
    
    private void initUI() {
        setTitle("自定义地图面板");
        setSize(1024, 768);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // 创建地图面板
        mapPane = new JMapPane(map);
        mapPane.setBackground(Color.WHITE);
        
        // 创建工具栏
        JToolBar toolbar = createToolbar();
        
        // 布局
        setLayout(new BorderLayout());
        add(toolbar, BorderLayout.NORTH);
        add(mapPane, BorderLayout.CENTER);
    }
    
    private JToolBar createToolbar() {
        JToolBar toolbar = new JToolBar();
        
        // 平移工具
        JButton panBtn = new JButton("平移");
        panBtn.addActionListener(e -> mapPane.setCursorTool(new PanTool()));
        toolbar.add(panBtn);
        
        // 放大工具
        JButton zoomInBtn = new JButton("放大");
        zoomInBtn.addActionListener(e -> mapPane.setCursorTool(new ZoomInTool()));
        toolbar.add(zoomInBtn);
        
        // 缩小工具
        JButton zoomOutBtn = new JButton("缩小");
        zoomOutBtn.addActionListener(e -> mapPane.setCursorTool(new ZoomOutTool()));
        toolbar.add(zoomOutBtn);
        
        // 全图
        JButton fullExtentBtn = new JButton("全图");
        fullExtentBtn.addActionListener(e -> mapPane.reset());
        toolbar.add(fullExtentBtn);
        
        return toolbar;
    }
    
    public static void main(String[] args) throws Exception {
        MapContent map = MapContentExample.createMapContent();
        
        SwingUtilities.invokeLater(() -> {
            CustomMapPanel panel = new CustomMapPanel(map);
            panel.setVisible(true);
        });
    }
}

12.5 高级渲染

12.5.1 渲染提示

public class RenderingHintsExample {
    
    public static void configureRenderer(StreamingRenderer renderer) {
        Map<Object, Object> rendererParams = new HashMap<>();
        
        // 渲染质量 vs 速度
        rendererParams.put(StreamingRenderer.OPTIMIZE_FTS_RENDERING_KEY, Boolean.TRUE);
        rendererParams.put(StreamingRenderer.SCALE_COMPUTATION_METHOD_KEY,
            StreamingRenderer.SCALE_ACCURATE);
        
        // 文本渲染
        rendererParams.put(StreamingRenderer.TEXT_RENDERING_KEY,
            StreamingRenderer.TEXT_RENDERING_STRING);
        
        // 线宽优化
        rendererParams.put(StreamingRenderer.LINE_WIDTH_OPTIMIZATION_KEY, Boolean.TRUE);
        
        renderer.setRendererHints(rendererParams);
        
        // Java2D 渲染提示
        RenderingHints hints = new RenderingHints(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON
        );
        hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        hints.put(RenderingHints.KEY_INTERPOLATION, 
            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        
        renderer.setJava2DHints(hints);
    }
}

12.5.2 渲染进度监听

import org.geotools.renderer.RenderListener;

public class RenderProgressExample {
    
    public static void renderWithProgress(MapContent map, BufferedImage image) {
        StreamingRenderer renderer = new StreamingRenderer();
        renderer.setMapContent(map);
        
        // 添加监听器
        renderer.addRenderListener(new RenderListener() {
            @Override
            public void featureRenderer(SimpleFeature feature) {
                // 每个要素渲染后调用
            }
            
            @Override
            public void errorOccurred(Exception e) {
                System.err.println("渲染错误: " + e.getMessage());
            }
        });
        
        Graphics2D g = image.createGraphics();
        Rectangle screenArea = new Rectangle(image.getWidth(), image.getHeight());
        renderer.paint(g, screenArea, map.getMaxBounds());
        g.dispose();
    }
}

12.6 瓦片渲染

12.6.1 分块渲染

public class TileRenderer {
    
    public static void renderTiles(MapContent map, File outputDir, 
            int tileSize, int zoomLevel) throws Exception {
        
        if (!outputDir.exists()) {
            outputDir.mkdirs();
        }
        
        ReferencedEnvelope mapBounds = map.getMaxBounds();
        
        // 计算瓦片数量
        double resolution = getResolution(zoomLevel);
        int cols = (int) Math.ceil(mapBounds.getWidth() / (tileSize * resolution));
        int rows = (int) Math.ceil(mapBounds.getHeight() / (tileSize * resolution));
        
        GTRenderer renderer = new StreamingRenderer();
        renderer.setMapContent(map);
        
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                // 计算瓦片范围
                double minX = mapBounds.getMinX() + col * tileSize * resolution;
                double minY = mapBounds.getMinY() + row * tileSize * resolution;
                double maxX = minX + tileSize * resolution;
                double maxY = minY + tileSize * resolution;
                
                ReferencedEnvelope tileBounds = new ReferencedEnvelope(
                    minX, maxX, minY, maxY,
                    mapBounds.getCoordinateReferenceSystem()
                );
                
                // 渲染瓦片
                BufferedImage tile = new BufferedImage(tileSize, tileSize, 
                    BufferedImage.TYPE_INT_ARGB);
                Graphics2D g = tile.createGraphics();
                
                renderer.paint(g, new Rectangle(tileSize, tileSize), tileBounds);
                g.dispose();
                
                // 保存瓦片
                String tilePath = String.format("%d/%d/%d.png", zoomLevel, col, row);
                File tileFile = new File(outputDir, tilePath);
                tileFile.getParentFile().mkdirs();
                ImageIO.write(tile, "PNG", tileFile);
            }
        }
    }
    
    private static double getResolution(int zoomLevel) {
        // Web Mercator 分辨率计算
        return 156543.03392 / Math.pow(2, zoomLevel);
    }
}

12.7 本章小结

本章详细介绍了 GeoTools 的地图渲染系统:

  1. 渲染架构

    • MapContent
    • GTRenderer
    • StreamingRenderer
  2. 图层管理

    • 创建图层
    • 图层顺序
    • 可见性控制
  3. 图片输出

    • BufferedImage 渲染
    • PNG/JPEG 保存
  4. Swing 集成

    • JMapFrame
    • JMapPane
    • 交互工具
  5. 高级渲染

    • 渲染提示
    • 进度监听
    • 瓦片渲染

关键要点

  • MapContent 管理图层和范围
  • StreamingRenderer 是主要的渲染器
  • 合理设置渲染提示优化性能
  • 大数据量考虑分块渲染

← 上一章:样式与符号化 | 返回目录 | 下一章:OGC服务客户端 →

posted @ 2025-12-29 11:40  我才是银古  阅读(3)  评论(0)    收藏  举报