概述
**前文**讲了在前端实现地图的导出,本文简单的通过java实现一下后端地图的导出。
实现效果
实现代码
代码语言:javascript复制package com.lzugis.image;
import sun.misc.BASE64Decoder;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
public class MapPrint {
static int TILE_SIZE = 256;
static double RES_ZERO = 156543.03392804097;
static double[] resolutions = new double[19];
static int ZOOM = 4;
public static void main(String[] args) {
long start = new Date().getTime();
System.out.println("开始出图。。。");
double res = RES_ZERO;
for (int i = 0; i < 19; i ) {
resolutions[i] = res;
res = res / 2;
}
double[] extent = {4816313.49458338, 1168266.6364252055, 19000980.555761307, 7826564.13063698};
int[] rowCol = getTileRowCol(extent);
int xmin = rowCol[0];
int xmax = rowCol[1];
int ymin = rowCol[2];
int ymax = rowCol[3];
int width = (xmax - xmin) * TILE_SIZE;
int height = (ymax - ymin) * TILE_SIZE;
try {
BufferedImage mergeImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics2D = mergeImage.createGraphics();
for (int i = xmin; i <= xmax ; i ) {
for (int j = ymin; j <= ymax ; j ) {
String destUrl = "http://webrd01.is.autonavi.com/appmaptile?x=" i "&y=" j "&z=" ZOOM "&lang=zh_cn&size=1&scale=1&style=8";
BufferedImage tile = getBufferedImageDestUrl(destUrl);
int x = (i - xmin) * TILE_SIZE;
int y = (j -ymin) * TILE_SIZE;
graphics2D.drawImage(tile, x, y, TILE_SIZE, TILE_SIZE, null);
}
}
// 添加wms图层
String extentStr = extent[0] "," extent[1] "," extent[2] "," extent[3];
String wmsUrl = "http://localhost:8081/geoserver/ecodrought/wms?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&FORMAT=image/png&TRANSPARENT=true&LAYERS=ecodrought:paddy-rice&CRS=EPSG:3857&SRS=EPSG:3857&WIDTH=" width "&HEIGHT=" height "&STYLES=&BBOX=" extentStr;
BufferedImage wms = getBufferedImageDestUrl(wmsUrl);
System.out.println(wmsUrl);
graphics2D.drawImage(wms, 0, 0, width, height, null);
// 添加地图标题
String mapTitle = "地图导出测试";
graphics2D.setColor(new Color(255,0,0,255));
Font font = new Font("微软雅黑", Font.BOLD, 24);
graphics2D.setFont(font);
int length = graphics2D.getFontMetrics(graphics2D.getFont()).charsWidth(mapTitle.toCharArray(), 0, mapTitle.length());
int posx = 60, posy = 80, offest = 15;
graphics2D.drawString(mapTitle, posx, posy);
graphics2D.setStroke(new BasicStroke(2.0f));
int x1 = posx - offest, y1 = posy - offest - 18, x2 = posx length offest, y2 = posy offest 6;
graphics2D.drawLine(x1, y1, x2, y1);
graphics2D.drawLine(x2, y1, x2, y2);
graphics2D.drawLine(x2, y2, x1, y2);
graphics2D.drawLine(x1, y2, x1, y1);
// 添加指北针
String base64 = "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAADPFJREFUeF7tnXmQXUUVh8 54zBhUSwZIgIqYBCS7vtCEi2IWiQiCEqxFAEFopQaSlYxlpS4sAoqipYgAqJGFAn7REFQ2SQiReEyDHl9esyEESKLLAa3oE4yzDtWwyMOw7x3l 6 y7x7q/JHavr8zulzvtfvLufdRqiOjs4AdvTsq8lDBUCHQ1ABUAHQ4Rno8OlXK0AFQIdnoMOnX60AFQAdngHH05dScoTkOUR0dlq3UfpElOhDnWhw2qA7yS6qQM1cpIYgSr8CIGfaogo0LrxUEETpVwCUBwATaWIIKgByLnCU 6gCTWKfCIIo/WoFiKqQ579HFaiF 9gQROlXAHgucJR8VIHa2MeCIEq/AiCqQhP XqvVptfr9WcSmrUcHlUg20vEKP0KgISVFEKcrrU L6GZLwCM7tlEdE4rBxUArioFAPPmzdtiw4YNqwFgKRGtcCEdVaCYPlpCEKVfrQAxM2yGCSGWIuI3AeA2IjogganPFeAl7UkhqABwUaWmhpRyEABmmv92d3dPHxgY KutfFSBEuq/AoIo/WoFiJlhIcSxiPi9ccNPJ6IvxTTPYgWYdCWoALCt0P8//b8DgLePk3uUiN5sKx9VoJT6m1aCKP1qBYiRYSnlBwHg2olDEXGBUuqeGBJZrgAvWwkqAGyq07QNw/AuZt5nEqnlRPQhGxdRBbLRNpeIzX8tZaoVICLDUsoDAeCWVsN6eno26 /vH01bqBgAmGv8s9LqR9lVAERkKAzDm5j54FbDEPF4pdTlUYlu9fcoAEyBpJTmk wFggqANpULw3ABM6 MKO4DRDTPJwBG2xcEFQBtKieEWI6IR0cVl5mF1trcI0h8xFkBXhL1AUEFQIuShWE4j5n/ELOiFxHR0phjXzYsCQA VoIKgBZVE0Jcjogfj1nU/xLRFjHHWgHgGoIKgEmqNnv27LeOjY0NJSkoMx ute5LYtMsZtuu4FYFcvV1UAEwScWklF8HgE8nLObtRLR/QhtzcpcKAFcrQQXAhIoJIbZDxDUA8OqkxRwdHd1haGjoL0nsbABwAUEFwIRq2SytzHxG0mYRWwBsIagAGAdAs FjGADekORTPG7sY0T0piS2LgCwgaACYFy1pJSnAsAFSQo4cSwiLlRK/TquhisA0kJQAfByAMx3/65xizfZOGa Wmu9OK6GSwDSQFAB0KxUGIbHMfN34hau3bjR0dHXDA0NrY j5RqApBBUADSrJKXsB4C5cYoWY8wJRBQLJh8AJIGgAgAAwjBczMxXxShsrCGIOKCUigWTLwDiQlAB8OKTNnPStnes6sYfNJuI6vGHl2PklHs/QBiGhzDzTz2k/1tE9EkPurlKTjkApJS3AsD7PWR1hIg296Cbq SUAkAIsQ8i3uUro0EQHFGv12/0pZ H7pQCQEppOn1Nx6 v4w4ieq8v8Tx0pwwAUsq3AcDvfScxCIJd6vX6I779ZKU/lQD4PgAs8Z04RDxTKXWubz9Z6U8JANI0fFgk HEieqOFfaFMpwQAUsoLASCzS7QgCPat1 veTjazJKT0ADQbPh4GgMwu0RDxGqVUZHdxloVM66v0AIRheC4zn542AWntNm7cuO2aNWvWpbUvil2pAZg/f/7m69evXwsA03NI6IlEdFkOfp26LDUAYRiexsznO81IfLEHiWhO/OHFHFlqAKSUfwKAXfJKLTPvqbU27xko7VFaAIQQJyHit3PO/MVEdErOMVi5Ly0AUspVAFCzmr298YaRkZGth4eHN9hL5aNQSgCEEMcg4o/ySdkrvB5JRNcVJJbEYZQSACnlvQDwzsSz9WNwJxHt50fav2rpAAjD8FBm/on/1CTyMIuI/pjIoiCDSweAlPKXAJD4N3s 842IZymlvujThy/tUgHgu HDIslPENGOFva5mZYKACml6cZZlFu22jhGxAOVUj8vYmztYioNAFk1fFgU8FoiOsrCPhfT0gAQhuEVzPyRXLIU02lXV9eOq1ateiLm8EIMKwUAM2fO3LWrq8v8zq/QByJ QimV993JRDkqBQBCiIsR8eREM8tncJ2IZufjOp3XwgMgpXw9ADwKAJulm2K2VkEQ7F2v13 Trdf03goPgBDiK4j42fRTzNaSmS/RWpdhtXohMYUGoNnw8TgAvC7bMlp5Gx0ZGekdHh7 l5VKRsaFBkBK XkAsN7EIaNcbnJjrla01kV5WNV2 kUHwLR7WW/ikDUAiPgrpdR7svabxl9hAQjD8GRmvjjNpIpgw8xztNYPFiGWdjEUGQAyL20uegLbxBdrJ9C851dIAArW8JG2Rk8S0Q4A0PbNoWnFXdkVFYD7EXFPV5PMUWeRqw0pfc2hcAAUtOEjbf6vJyKfP1dPG9cmu8IBIKW8AwD2tZ5ZcQRmEJFpXy/kUSgAhBDvNpdQhcxU qBOJaJvpDf3a1koAKSUptfvUL9TzlxdEVHe7estJ10YABJu6ZJ5FW0cIuJ Sqk7bTR82RYGACnljwHAatNGX0lyoHspEZ3kQMe5RCEAKEvDh0X2n /u7t7exe7kFjFMaloIAKSUlwLACa4nVyQ9RDxOKfXdIsVkYskdgGbDh9mWJShachzHczcRTbZfsWM3yeRyB0AIcT4inpYs7HKODoJgr3q9/tsiRZ8rADvttNO0rbba6ikA2LpISfEVCzN/VWtdqO6mXAEoa8OHBSBPPffcczuvXbt2xELDqWneAJh2L/PErGMOZj5aa31NUSacGwBhGJ7EzKXqoXdUtBuI6AOOtKxlcgNASrkaAHaznkEJBWx2J3c93VwAkFJ GACudD2ZEul9gYi XIR48wLAvNXbvN27Uw8iorAIk88cAI9buhQhn0liOIiIbkli4GNs5gBIKe8GgIU JlMyzWVEdGzeMWcKwBRt EhbwzEA2JmIHksr4MIuUwCklDcDwEEuAp8iGqcQUa6/fcgMgKnc8GEB40oiereFvbVpZgBIKc1OnrE3YbaeWUkEEHGBUuqevMLNBAAhxAxEfCivSRbZLyJeqJT6VF4xZgJAJzR8WBTw6Z6ent36 /v/aaGR2tQ7ALVabXqj0Xg6dYQdYIiIH1VK/TCPqXoHoGxv MijCMx8s9b6kDx8ewWg2fBh9tXZMo/Jlclno9GYOzg4OJB1zF4BkFJ DgAK8dAj68Qm9cfM52qtz0xqZzveNwBPAsB2tkF2gj0iaqWU QVRI8v5egOgIFu6ZJlLa1/MfLjWus9aKIGANwCklOa6f0aCWKqhAFcRkemVyOzwAkDV8JG6fg1m3k1rPZxaIaGhFwDCMHzAvCQpYSzV8Bcz8BkiuiCrZDgHoKgNH YkCwCWNRO7pMAvoLqPiDLbD8k5AFLKlQCwICuCI/yYFzQtC4JgWb1ev3/82Fqttlej0VgCAOaf8zxYzn9/IrrdUiOWudOJSylNp4/p Mn7uM8Uvre398qVK1c 3y6YhQsXvmrdunXHNEF4R96BN/1fRkQnZhGLawDybPj4R3OJN61WqXbwklLObIJgVoXXZlGAFj6eMZtiEpH3ZyjOAKjVanMbjUZ/1klj5l Ywru fhZCmL2JliDi 7Kek/GHiMcrpS737dsZABm/4ePPiGhO6JYrpR72maQwDM3m1IuZ2awKWb63 DYiOsDn3F4AzYWDDBs rmPmK7TWt7mIO6mGEGJ/8 gWADJ5918QBPMnnrwmjTlqvBMApJSXAICvkxZllvju7u4bBgYGzIskcj/mzJmz/ejo6BHN8wVvP/BAxK8ppby O8EaAE8NH2PMvAwRryEic1lZ2MNc TDzUYhoviK6HAf6UE9Pzx79/f3/cay7Sc4aAMcNH/cy85VjY2MrVq9e/ayvSfvQ3X333bfp6uo6DBHNJeW7XPlAxMVKqatd6U3UsQJgxowZPdOmTTOXX9MsAvwbAPwgCII 3993FjEmMm3eZDJXER z3e6GmW/UWpuvGy HFQCWDR 3MvP1XV1dpvD/9jK7nEVrtdqWY2NjixDRvA/gwJThcKPRCAcHB82tbOeHLQDmhsW2CaJ6BBGXmyU j/anBHE6Hzpr1qw55iuCmc1vI3ZO4oCZz9Ban5fEJu7Y1AAIIU5ERHP2H e4HgD6ent7V0Tdmo0jVuYxzVvPhzU3wY77ppB IvLyc/rUAEgpzTPrt7QpxoPM3IeIfWlvzZa50HFiN7eemdl8RZjzhT3a2TDzwVrrn8XRTTImFQBSSvNOX/Nu34nHRgC4FhFXKKVuShJIp49tPkY3K8ORLXZJvYKIzEml0yMtAOae/9xxkZhHrebTbgrv9das09kXUMzcembml74i9hoX4t8bjcYeg4ODZhtdZ0diAIQQByOi XQ/awreaDT68ro16ywLBRUyt56DIFjUBGIbAFhKRBe5DDcxAM03fKzo7u7uK8qtWZcJKaJW89azOU84zPXPyf8H8Ry1vYSr14wAAAAASUVORK5CYII=";
int w =50, h = 50, x = width - w - posx;
graphics2D.drawImage(base64ToBufferedImage(base64), x , posx, w, h, null);
// 保存图片
generateSaveFile(mergeImage, "merge.png");
long end = new Date().getTime();
System.out.println(String.format("出图结束,耗时%sms", end - start));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取切片行列号
* @param {double[]} extent - 出图四至,格式为[xmin, ymin, xmax, ymax]
* @return {int[]} - 切片的行列号范围
*/
public static int[] getTileRowCol (double[] extent) {
if(extent.length != 4) return new int[]{};
double originY = RES_ZERO * TILE_SIZE / 2;
double originX = -originY;
double res = resolutions[ZOOM] * TILE_SIZE;
int xmin = (int) Math.floor((extent[0] - originX) / res);
int xmax = (int) Math.ceil((extent[2] - originX) / res);
int ymax = (int) Math.ceil((originY - extent[1]) / res);
int ymin = (int) Math.floor((originY - extent[3]) / res);
// 往周围扩充一圈
return new int[]{ xmin 1, xmax - 1, ymin 1, ymax - 1 };
}
/**
* 远程图片转BufferedImage
* @return {BufferedImage} - 返回图片BufferedImage
*/
public static BufferedImage getBufferedImageDestUrl(String destUrl) {
HttpURLConnection conn = null;
BufferedImage image = null;
try {
URL url = new URL(destUrl);
conn = (HttpURLConnection) url.openConnection();
if (conn.getResponseCode() == 200) {
image = ImageIO.read(conn.getInputStream());
return image;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
conn.disconnect();
}
return image;
}
/**
* base64转BufferedImage
* @param {String} base64
* @return {BufferedImage}
* @throws Exception
*/
public static BufferedImage base64ToBufferedImage(String base64) throws Exception {
BASE64Decoder decoder = new BASE64Decoder();
byte[] result = decoder.decodeBuffer(base64);
InputStream stream = new ByteArrayInputStream(result);
BufferedImage bufferedImage = ImageIO.read(stream);
return bufferedImage;
}
/**
* 输出图片
* @param buffImg 图像拼接叠加之后的BufferedImage对象
* @param savePath 图像拼接叠加之后的保存路径
*/
public static void generateSaveFile(BufferedImage buffImg, String savePath) {
int temp = savePath.lastIndexOf(".") 1;
try {
File outFile = new File(savePath);
if(!outFile.exists()){
outFile.createNewFile();
}
ImageIO.write(buffImg, savePath.substring(temp), outFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
实现分析
上面的实现中,实现了两类图层的叠加:底图切片和WMS图层的加载。底图切片的核心加载逻辑是通过传入的四至和地图级别计算需要叠加的切片的x和y的范围,并计算合并后的图片的大小,wms加载的逻辑是通过传入的四至和计算好的图片大小,再通过参数url和layers获取对应的图层的图片,再将两个图片合并。