這裏沒有采用geo-tools庫,使用一種較爲簡單的方法,原理類似於arcmap中對圖片進行配準的過程。
最終效果如下:
要完成這樣的一幅圖片,繪製等值面的數據及等值面的配色從數據庫中讀取空間數據轉換而來的GeoJSON數據,不使用從數據庫直接獲取的數據進行繪圖,主要是等值面的數據還要作爲接口數據返回給前臺,方便起見就統一使用GeoJSON來描述要繪製的等值面
5.3.1 準備工作
- 繪製的底圖
- 底圖的經緯度範圍
5.3.2 使用graphics2D進行繪製
繪製圖片的代碼
public void groundWaterStageLuffSurfaceAllTaskInit(String beginTime, String endTime, String prictureCode) {
Result<FeatureCollection<PolygonFeature>> result = groundWaterStageService.queryStageSurfaceLuffDay(beginTime,endTime);
FeatureCollection<PolygonFeature> data = result.getData();
String title = "XXXXXXXXXXXXXXXXXXXXXXX等值面分析成果圖";
String subTitle = "基準時間: " + beginTime+ " , 對比時間: "+ endTime;
GroundWaterStageLuffSurfaceAllImageUtil imageUtil = new GroundWaterStageLuffSurfaceAllImageUtil(groundWaterStageLuffSurfaceAllConfig);
imageUtil.draw(groundWaterStageLuffSurfaceAllConfig.getBaseImagePath(),
groundWaterStageLuffSurfaceAllConfig.getImageSaveDir() + File.separator +beginTime+"-"+prictureCode+".jpg",
data, title, subTitle);
// 將生成圖片的結果記錄到數據庫中
Statisticsp entity = new Statisticsp();
entity.setArty(beginTime+ "-"+prictureCode+".jpg");
entity.setPictureCode(prictureCode);
entity.setTm(beginTime);
entity.setTs(new Date());
statisticspDao.insert(entity);
}
GroundWaterStageLuffSurfaceAllImageUtil類的代碼如下:
package com.summit.gis.util.img;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.summit.gis.config.GroundWaterStageLuffSurfaceAllConfig;
import com.summit.gis.constants.SystemConstants;
import com.summit.gis.util.pojo.FeatureCollection;
import com.summit.gis.util.pojo.PolygonFeature;
import lombok.extern.slf4j.Slf4j;
/**
*
* @ClassName: GroundWaterStageLuffSurfaceAllImageUtil
* @Description:地下水水位變幅日分析等值面
* @author: zhangcj
* @date: 2019年9月26日 下午7:44:08
*/
@Slf4j
public class GroundWaterStageLuffSurfaceAllImageUtil {
@Autowired
private GroundWaterStageLuffSurfaceAllConfig bi;
public GroundWaterStageLuffSurfaceAllImageUtil() {}
public GroundWaterStageLuffSurfaceAllImageUtil(GroundWaterStageLuffSurfaceAllConfig bi) {
super();
this.bi = bi;
}
/**
* 底圖寬度
*/
private int width;
/**
* 底圖的高度
*/
private int height;
/**
* 底圖x軸方向的margin
*/
private int marginWidth;
/**
* 底圖y軸方向的margin
*/
private int marginHeight;
/**
* x方向的比例因子
*/
private double abscissaScale;
/**
* y方向的比例因子
*/
private double ordinateScale;
/**
*
* TODO(導入背景圖片讀到緩衝區)
* @param imgName
* @return
* @return: BufferedImage
* @throws
*/
public BufferedImage loadBackGroundImage(String imgName) {
BufferedImage bimage = null;
try {
bimage = ImageIO.read(new File(imgName));
width = bimage.getWidth();
height = bimage.getHeight();
// 計算橫軸上的比例因子
abscissaScale = (bi.getXMax() - bi.getXMin()) / width;
// 計算縱軸上的比例因子
ordinateScale = (bi.getYMax() - bi.getYMin()) / height;
} catch (IOException e) {
System.out.println(e.getMessage());
}
return bimage;
}
/**
* @param subTitle
* @param title
*
* TODO(在給定底圖繪製圖片)
* @param backgroundImagePath 背景圖片地址
* @param imageSavePath 圖片保存地址
* @param geojson 要繪製的geojson
* @param title 專題圖名稱
* @return: void
* @throws
*/
public void draw(String backgroundImagePath, String imageSavePath, FeatureCollection<PolygonFeature> geojson, String title, String subTitle) {
try {
// 讀取底圖文件
BufferedImage bufferedImage = loadBackGroundImage(backgroundImagePath);
// 得到Graphics2D 對象
BufferedImage saveImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2d = saveImage.createGraphics();
graphics2d.setBackground(Color.WHITE);
// 獲取底圖寬高
width = bufferedImage.getWidth();
height = bufferedImage.getHeight();
marginWidth = width / 100;
marginHeight = height / 100;
// 繪製等值線
drawIsopleth(graphics2d , geojson);
// 繪製標題
drawTitle(graphics2d,title,subTitle);
graphics2d.dispose();
File f = new File(imageSavePath);
if (f.exists()) {
f.delete();
}
ImageIO.write(saveImage, "PNG", f);
mergeImage(imageSavePath,backgroundImagePath, 0.1f);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
* 合併圖片
* @param baseImagePath
* @param overredImagePaht
* @param alpha
* @return: void
* @throws
*/
public void mergeImage(String baseImagePath , String overredImagePaht, float alpha) {
try {
BufferedImage baseImage = ImageIO.read(new File(baseImagePath));
BufferedImage overredImage = ImageIO.read(new File(overredImagePaht));
Graphics2D createGraphics = baseImage.createGraphics();
createGraphics.drawImage(overredImage,0,0,width,height,null);
ImageIO.write(baseImage, "PNG", new File(baseImagePath));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
*
* 繪製標題
* @param graphics2d
* @param title
* @param subTitle
* @return: void
* @throws
*/
private void drawTitle(Graphics2D graphics2d, String title, String subTitle) {
// 繪製主標題
graphics2d.setColor(new Color(0x0000ff));
graphics2d.setStroke(new BasicStroke(2));
graphics2d.setFont(new Font(SystemConstants.TITLE_FONT, Font.PLAIN, 72));
int x = 25 * marginWidth;
int y = 12 * marginHeight;
graphics2d.drawString(title, x, y);
//繪製副標題
graphics2d.setColor(Color.black);
graphics2d.setStroke(new BasicStroke(2));
graphics2d.setFont(new Font(SystemConstants.TITLE_FONT, Font.PLAIN, 36));
graphics2d.drawString(subTitle, 38 * marginWidth, 17 * marginHeight);
}
/**
*
* TODO(動態繪製圖例)
* @param graphics2d 筆刷工具
* @param rangeStr 分類字符串
* @param clsStr 顏色字符串
* @return: void
* @throws
*/
private void drawLegend(Graphics2D graphics2d, String rangeStr, String clsStr) {
// 繪製外邊框
drawLegendContainer(graphics2d);
// 繪製圖例標題
drawLegendTitle(graphics2d, "圖例");
// 繪製圖例內容
drawLegendContent(graphics2d, rangeStr, clsStr);
}
/**
*
* TODO(繪製圖例容器)
* @param graphics2d
* @return: void
* @throws
*/
private void drawLegendContainer(Graphics2D graphics2d) {
graphics2d.setStroke(new BasicStroke(1));
graphics2d.setColor(new Color(0xe8e8e8));
graphics2d.fillRect(6 * marginWidth, 65 * marginHeight, 11 * marginWidth, 32 * marginHeight);
;
}
/**
*
* TODO(繪製圖例標題)
* @param graphics2d
* @param legendTitle
* @return: void
* @throws
*/
private void drawLegendTitle(Graphics2D graphics2d, String legendTitle) {
// 設置圖例標題顏色
graphics2d.setColor(Color.black);
// 設置圖例標題字體
graphics2d.setFont(new Font(SystemConstants.TITLE_FONT, Font.BOLD, 32));
graphics2d.drawString(legendTitle, 10 * marginWidth, 68 * marginHeight);
}
/**
*
* TODO(這裏用一句話描述這個方法的作用)
* @param graphics2d
* @param rangeStr
* @param clsStr
* @return: void
* @throws
*/
private void drawLegendContent(Graphics2D graphics2d, String rangeStr, String clsStr) {
// 裝配等級範圍數組
String[] ranges = rangeStr.split(",");
ArrayUtils.reverse(ranges);
// 獲取顏色值數組
String[] colors = clsStr.split(",");
ArrayUtils.reverse(colors);
for (int j = 0; j < colors.length; j++) {
drawLegendRow(graphics2d, ranges[j], colors[j], j);
}
}
/**
*
* TODO(繪製每一行的圖例)
* @param graphics2d
* @param rangeLevel
* @param color
* @param index
* @return: void
* @throws
*/
private void drawLegendRow(Graphics2D graphics2d, String rangeLevel, String color, int index) {
// 繪製顏色
graphics2d.setColor(new Color(Integer.parseInt(color, 16)));
graphics2d.fillRect(7 * marginWidth, 70 * marginHeight + 4 * index * marginHeight, 3 * marginWidth,
2 * marginHeight);
// 繪製標註
graphics2d.setColor(Color.black);
graphics2d.setFont(new Font(SystemConstants.TITLE_FONT, Font.PLAIN, 24));
graphics2d.drawString(rangeLevel, 12 * marginWidth, (int)(71.5 * marginHeight) + 4 * index * marginHeight);
}
/**
*
* TODO(繪製等值線)
* @param graphics2d
* @param geojson
* @return: void
* @throws
*/
public void drawIsopleth(Graphics2D graphics2d, FeatureCollection<PolygonFeature> geojson) {
if(geojson == null) {
log.info("未獲取到等值面信息");
return ;
}
// TODO 後續修改爲調用傳入要繪製的GeoJson數據進行繪製
List<PolygonFeature> features = geojson.getFeatures();
features.stream().forEach(item->{
JSONObject properties = new JSONObject(item.getProperties());
// 獲取每個等值線面的顏色值
String color = properties.getString("color");
if(StringUtils.isNotBlank(color)) {
graphics2d.setColor(new Color(Integer.parseInt(color, 16)));
JSONObject geometry = new JSONObject(item.getGeometry());
String type = geometry.getString("type");
if("MultiPolygon".equalsIgnoreCase(type)) {
//繪製多面
drawMultiPolygon(graphics2d,geometry.getJSONArray("coordinates"));
}
}
});
// drawLegend(graphics2d,featureCollections.getString("ranges"),
// featureCollections.getString("colors"));
}
/**
*
* 繪製多面
* @param graphics2d
* @param color
* @param jsonArray
* @return: void
* @throws
*/
private void drawMultiPolygon(Graphics2D graphics2d, JSONArray coordinates) {
JSONArray jsonArray = coordinates.getJSONArray(0);
JSONArray coordinate = jsonArray.getJSONArray(0);
int[] xArray = new int[coordinate.size()];
int[] yArray = new int[coordinate.size()];
for (int i = 0; i < coordinate.size(); i++) {
JSONArray point = coordinate.getJSONArray(i);
// 獲取點的地理經度
double lon = point.getDouble(0);
// 獲取點的地理緯度
double lat = point.getDoubleValue(1);
double[] result = transferCoord(new double[] { lon, lat });
// 座標轉換 TODO 這裏直接對經緯度座標取整,存在較大誤差,後續需要改進
xArray[i] = (int)result[0];
yArray[i] = (int)result[1];
}
// 組裝點成面 ,繪圖
Polygon polygon = new Polygon(xArray,yArray,xArray.length);
graphics2d.fill(polygon);
}
/**
*
* TODO(繪製線)
* @param graphics2d graphics上下文對象
* @param coordinates 要繪製線的座標數組
* @return: void
* @throws
*/
private void drawLine(Graphics2D graphics2d, JSONArray coordinates) {
double[] xArray = new double[coordinates.size()];
double[] yArray = new double[coordinates.size()];
for (int i = 0; i < coordinates.size(); i++) {
JSONArray point = coordinates.getJSONArray(i);
// 獲取點的地理經度
double lon = point.getDouble(0);
// 獲取點的地理緯度
double lat = point.getDoubleValue(1);
double[] result = transferCoord(new double[] { lon, lat });
// 座標轉換
xArray[i] = result[0];
yArray[i] = result[1];
}
// 組裝點,畫圖
for (int j = 0; j < xArray.length - 1; j++) {
Line2D.Double line2d = new Line2D.Double(xArray[j], yArray[j], xArray[j + 1], yArray[j + 1]);
graphics2d.draw(line2d);
}
}
/**
* 將經緯度座標變換到圖片像素座標
*
* @param coord
* @return
*/
public double[] transferCoord(double[] coord) {
double lon = (coord[0] - bi.getXMin()) / abscissaScale;
double lat = height - (coord[1] - bi.getYMin()) / ordinateScale;
return new double[] { lon, lat };
}
/**
*
* TODO(讀取本地json文件,轉換爲FeatureCollection對象)
* @param pathname 本地json文件的地址
* @return
* @return: FeatureCollection 輸出的FeatureCollection對象
* @throws
*/
public FeatureCollection readGeoJson(String pathname) {
try {
FileReader reader = new FileReader(pathname);
BufferedReader br = new BufferedReader(reader);
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
// 一次讀入一行數據
sb.append(line);
}
br.close();
String jsonStr = sb.toString();
// 將GeoJSON字符串轉換爲FeatureCollection對象
FeatureCollection featureCollection = JSONObject.parseObject(jsonStr,FeatureCollection.class);
return featureCollection;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
GroundWaterStageLuffSurfaceAllConfig爲讀取底圖信息的配置類
package com.summit.gis.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;
/**
*
* @ClassName: GroundWaterStageSurfaceAllConfig
* @Description: 全國地下水水位等值面動態分析配置
* @author: zhangcj
* @date: 2019年9月24日 上午10:07:10
*/
@Component
@ConfigurationProperties(prefix = "ground-water-stage-luff-surface-all")
@Data
public class GroundWaterStageLuffSurfaceAllConfig {
/**
* 底圖最小經度值
*/
private double xMin ;
/**
* 底圖最大經度值
*/
private double xMax ;
/**
* 底圖最小緯度值
*/
private double yMin ;
/**
* 底圖最大緯度值
*/
private double yMax ;
/**
* 繪製好的圖片保存地址
*/
private String imageSaveDir;
/**
* 定時任務cron表達式
*/
private String cron;
/**
* 底圖位置
*/
private String baseImagePath;
}
要進行繪製的等值面的數據
isosurface.json
5.3.3 該繪圖方式的改進
該方式畫圖精度不高,需要對底圖的經緯度範圍進行微調,後續考慮使用geotools提供的庫來實現此類圖片的繪製