第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 的地图渲染系统:
-
渲染架构
- MapContent
- GTRenderer
- StreamingRenderer
-
图层管理
- 创建图层
- 图层顺序
- 可见性控制
-
图片输出
- BufferedImage 渲染
- PNG/JPEG 保存
-
Swing 集成
- JMapFrame
- JMapPane
- 交互工具
-
高级渲染
- 渲染提示
- 进度监听
- 瓦片渲染
关键要点
- MapContent 管理图层和范围
- StreamingRenderer 是主要的渲染器
- 合理设置渲染提示优化性能
- 大数据量考虑分块渲染

浙公网安备 33010602011771号