ShapeUtils.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. package cn.com.taiji.tile.util;
  2. import cn.com.taiji.common.model.LayerStyleView;
  3. import cn.com.taiji.tile.style.ShapeStyle;
  4. import cn.hutool.core.util.BooleanUtil;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.apache.commons.lang3.StringUtils;
  7. import org.locationtech.jts.geom.Coordinate;
  8. import org.locationtech.jts.geom.Geometry;
  9. import org.locationtech.jts.io.ParseException;
  10. import org.locationtech.jts.io.WKBReader;
  11. import org.locationtech.jts.io.WKTReader;
  12. import org.locationtech.jts.io.WKTWriter;
  13. import org.springframework.beans.BeanUtils;
  14. import javax.imageio.ImageIO;
  15. import java.awt.*;
  16. import java.awt.image.BufferedImage;
  17. import java.io.IOException;
  18. import java.net.URL;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Objects;
  22. import java.util.concurrent.CopyOnWriteArrayList;
  23. import java.util.function.Consumer;
  24. /**
  25. * @author zzyx 2024/1/5
  26. */
  27. @Slf4j
  28. public class ShapeUtils {
  29. public static final int WIDTH = 256;
  30. public static final int HEIGHT = 256;
  31. public final static WKTReader WKT_READER = new WKTReader();
  32. private static void setImage(String imageUrl, Consumer<BufferedImage> imageConsumer) {
  33. if (StringUtils.isNotBlank(imageUrl)) {
  34. try {
  35. URL url = new URL(imageUrl);
  36. BufferedImage image = ImageIO.read(url);
  37. imageConsumer.accept(image);
  38. } catch (IOException e) {
  39. log.error("Error loading image from URL: {}", imageUrl, e);
  40. }
  41. }
  42. }
  43. private static void setColorFromHex(String hexColor, Consumer<Color> colorConsumer) {
  44. if (StringUtils.isNotBlank(hexColor)) {
  45. try {
  46. int[] rgba = hexToRgba(hexColor, 255);
  47. colorConsumer.accept(new Color(rgba[0], rgba[1], rgba[2], rgba[3]));
  48. } catch (IllegalArgumentException e) {
  49. log.error("Invalid hex color format: {}", hexColor, e);
  50. }
  51. }
  52. }
  53. private static void setDashPattern(String dashPatternStr, Consumer<float[]> dashPatternConsumer) {
  54. if (StringUtils.isNotBlank(dashPatternStr)) {
  55. String[] split = dashPatternStr.split(",");
  56. float[] dashPatternArr = new float[split.length];
  57. for (int i = 0; i < split.length; i++) {
  58. dashPatternArr[i] = Float.parseFloat(split[i]);
  59. }
  60. dashPatternConsumer.accept(dashPatternArr);
  61. }
  62. }
  63. private static void drawPolygonShape(double[] bbox, Graphics2D graphics, Geometry geometry, Map<String, Object> poi, ShapeStyle shapeStyle) {
  64. drawPolygonToTile(shapeStyle, geometry, graphics, bbox);
  65. //是否需要设置字名称
  66. if (BooleanUtil.isTrue(shapeStyle.getIsShowName()) && StringUtils.isNotBlank(poi.get("name").toString())) {
  67. org.locationtech.jts.geom.Point centroid = geometry.getCentroid();
  68. int[] pxy = TileUtils.toPixelXY(centroid.getX(), centroid.getY(), bbox[0], bbox[1], bbox[2], bbox[3]);
  69. //设置字体样式
  70. Font font = new Font("Arial", Font.TRUETYPE_FONT, shapeStyle.getFontSize());
  71. String s = poi.get("name").toString();
  72. graphics.setColor(shapeStyle.getFontColor());
  73. graphics.setFont(font);
  74. graphics.drawString(s, pxy[0], pxy[1]);
  75. //设置面的图标
  76. BufferedImage cpi = shapeStyle.getCenterPointImage();
  77. if (cpi != null) {
  78. graphics.drawImage(TileUtils.scaleWidthHeight(cpi, 13, 13), pxy[0], pxy[1], null);
  79. }
  80. }
  81. }
  82. public static void drawLineShape(double[] bbox, Graphics2D graphics, Geometry geometry, ShapeStyle shapeStyle) {
  83. int[] xpoint = new int[(geometry.getCoordinates()).length];
  84. int[] ypoint = new int[(geometry.getCoordinates()).length];
  85. convertCoordinatesToPixels(geometry.getCoordinates(), xpoint, ypoint, bbox);
  86. graphics.setColor(shapeStyle.getColor());
  87. graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  88. if (Objects.equals(shapeStyle.getLineType(), 2)) {
  89. graphics.setStroke(new BasicStroke(shapeStyle.getWidth(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, shapeStyle.getDashPattern(), 0.0f));
  90. } else if (Objects.equals(shapeStyle.getLineType(), 3)) {
  91. graphics.setStroke(new BasicStroke(shapeStyle.getWidth(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, shapeStyle.getDashPattern(), 0.0f));
  92. } else {
  93. graphics.setStroke(new BasicStroke(shapeStyle.getWidth()));
  94. }
  95. //端点的样式可以是圆形 (CAP_ROUND)、平直 (CAP_BUTT) 或方形 (CAP_SQUARE)
  96. // 连接样式:线段之间的连接可以设置为斜接 (JOIN_MITER)、圆角 (JOIN_ROUND) 或平直 (JOIN_BEVEL)。
  97. // graphics.setStroke(new BasicStroke(10f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
  98. graphics.drawPolyline(xpoint, ypoint, geometry.getCoordinates().length);
  99. }
  100. public static void drawPointShape(double[] bbox, Graphics2D graphics, Geometry geometry, Map<String, Object> poi, ShapeStyle shapeStyle) {
  101. int[] pxy = TileUtils.toPixelXY(geometry.getCoordinate().x, geometry.getCoordinate().y, bbox[0], bbox[1], bbox[2], bbox[3]);
  102. // int dx = (int) (256.0D * (geometry.getCoordinate().x - bbox[0]) / (bbox[2] - bbox[0]));
  103. // int dy = 256 - (int) (256.0D * (geometry.getCoordinate().y - bbox[1]) / (bbox[3] - bbox[1]));
  104. // int[] pxy = new int[]{dx-10, dy-10};
  105. //设置边框 实现轮廓
  106. graphics.setColor(shapeStyle.getColor());
  107. graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  108. //设置字体
  109. //设置字体样式
  110. if (BooleanUtil.isTrue(shapeStyle.getIsShowName()) && StringUtils.isNotBlank(poi.get("name").toString())) {
  111. Font font = new Font("Arial", Font.BOLD, shapeStyle.getFontSize());
  112. String s = poi.get("name").toString();
  113. graphics.setColor(shapeStyle.getFontColor());
  114. graphics.setFont(font);
  115. graphics.drawString(s, pxy[0], pxy[1]);
  116. }
  117. BufferedImage pointMarkerImage = shapeStyle.getPointMarkerImage();
  118. if (pointMarkerImage != null) {
  119. graphics.drawImage(pointMarkerImage, pxy[0], pxy[1], null);
  120. } else {
  121. drawPointShape(shapeStyle, graphics, pxy);
  122. }
  123. }
  124. public static void convertCoordinatesToPixels(Coordinate[] coordinates, int[] xPoints, int[] yPoints, double[] bbox) {
  125. // 坐标转换逻辑
  126. for (int i = 0; i < coordinates.length; i++) {
  127. Coordinate c = coordinates[i];
  128. //坐标转换:对于每个坐标 c,代码调用 TileUtils.toPixelXY 方法,将地理坐标(经纬度)转换为像素坐标。这个转换考虑了一个称为 bbox(边界框)的地理区域,这个区域定义了地理坐标到像素坐标转换的上下文。bbox 通常包含边界框的最小经度、最小纬度、最大经度和最大纬度。
  129. int[] pxy = TileUtils.toPixelXY(c.getX(), c.getY(), bbox[0], bbox[1], bbox[2], bbox[3]);
  130. xPoints[i] = pxy[0];
  131. yPoints[i] = pxy[1];
  132. }
  133. }
  134. /**
  135. * 面填充样式
  136. * 在地理信息系统(GIS)和空间数据处理领域中,Geometry 类是一个常用的概念,用于表示空间对象的形状和位置。这个类通常包含各种方法来操作和查询几何形状。getCoordinates() 方法是 Geometry 类中的一个方法,它的作用是获取构成该几何形状的所有坐标点。
  137. * 当你调用 Geometry 对象的 getCoordinates() 方法时,它会返回一个坐标数组,这个数组包含了组成该几何形状的所有顶点坐标。这些坐标通常是 Coordinate 对象,每个 Coordinate 包含 x(经度)、y(纬度)和可选的 z(高度)值。
  138. * 例如,如果你的 Geometry 对象是一个多边形,getCoordinates() 方法将返回构成该多边形边界的所有顶点坐标。如果是一条线,它将返回线的所有顶点坐标。
  139. * 这个方法在进行空间分析和几何处理时非常有用,比如当你需要遍历几何形状的所有顶点、计算形状的边界、或者在不同几何形状之间进行坐标转换时。
  140. * <p>
  141. * Coordinate 是在地理信息系统(GIS)和计算机图形学中常用的一个基本概念,用于表示空间中的点。在多种编程环境和库中,Coordinate 类或结构体通常用于存储和操作这些点的位置信息。其主要作用和特点包括:
  142. * 存储位置信息:Coordinate 通常包含至少两个维度的数据,即 x(经度)和 y(纬度)。在三维空间中,它还可以包含 z(高程或深度)值。
  143. * 空间分析和计算:在 GIS 和计算机图形学应用中,Coordinate 用于执行各种空间分析和计算,如测量距离、定义几何形状(如线和多边形)的边界等。
  144. * 数据转换和处理:Coordinate 在进行坐标系转换、地图投影、空间查询等操作中也非常重要。
  145. * 可视化和绘图:在地图制作和空间数据可视化中,Coordinate 用于确定应在地图上绘制的点的确切位置。
  146. * 在不同的编程库和框架中,Coordinate 类可能还包含了其他辅助方法,如计算两个坐标之间的距离、比较坐标点是否相等等。例如,在 Java 的 JTS Topology Suite(一个常用的空间数据处理库)中,Coordinate 类就提供了这样的功能。
  147. */
  148. public static void drawPolygonToTile(ShapeStyle shapeStyle, Geometry shape, Graphics2D graphics, double[] bbox) {
  149. //普通面
  150. if (shape != null && shape.getCoordinates().length > 0) {
  151. int[] xpoint = new int[(shape.getCoordinates()).length];
  152. int[] ypoint = new int[(shape.getCoordinates()).length];
  153. convertCoordinatesToPixels(shape.getCoordinates(), xpoint, ypoint, bbox);
  154. //设置样式
  155. //2.设置抗锯齿
  156. graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  157. //是否填充
  158. if (BooleanUtil.isTrue(shapeStyle.getIsFill())) {
  159. //fill 是将设置的颜色全部填充
  160. graphics.setColor(shapeStyle.getColor());
  161. //设置背景面
  162. setBackgroundImage(graphics, shapeStyle);
  163. Polygon polygon = new Polygon(xpoint, ypoint, shape.getNumPoints());
  164. //填充
  165. graphics.fill(polygon);
  166. //设置边框颜色
  167. setBorderStyle(graphics, shapeStyle);
  168. //drawPolygon 是透明的只会出来边框
  169. graphics.drawPolygon(polygon);
  170. } else {
  171. setBorderStyle(graphics, shapeStyle);
  172. graphics.drawPolygon(xpoint, ypoint, (shape.getCoordinates()).length);
  173. }
  174. }
  175. }
  176. private static void setBackgroundImage(Graphics2D graphics, ShapeStyle shapeStyle) {
  177. BufferedImage backgroundImage = shapeStyle.getBgImage();
  178. if (backgroundImage != null) {
  179. TexturePaint texturePaint = new TexturePaint(backgroundImage, new Rectangle(0, 0, backgroundImage.getWidth(), backgroundImage.getHeight()));
  180. graphics.setPaint(texturePaint);
  181. }
  182. }
  183. private static void setBorderStyle(Graphics2D graphics, ShapeStyle shapeStyle) {
  184. //不填充只显示边框颜色
  185. graphics.setColor(shapeStyle.getBorderColor());
  186. // 设置虚线:5像素实线,5像素空白
  187. if (BooleanUtil.isTrue(shapeStyle.getIsDashed())) {
  188. Stroke dashed = new BasicStroke(shapeStyle.getBorderSize(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, shapeStyle.getDashPattern(), 0);
  189. graphics.setStroke(dashed);
  190. } else {
  191. graphics.setStroke(new BasicStroke(shapeStyle.getBorderSize()));
  192. }
  193. }
  194. public static int[] hexToRgba(String hexColor, int alpha) {
  195. hexColor = hexColor.replace("#", "");
  196. int red = Integer.valueOf(hexColor.substring(0, 2), 16);
  197. int green = Integer.valueOf(hexColor.substring(2, 4), 16);
  198. int blue = Integer.valueOf(hexColor.substring(4, 6), 16);
  199. return new int[]{red, green, blue, alpha};
  200. }
  201. private static void drawPointShape(ShapeStyle shapeStyle, Graphics2D graphics, int[] pxy) {
  202. if (BooleanUtil.isTrue(shapeStyle.getIsDashed())) {
  203. //如果是
  204. Stroke dashed = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, shapeStyle.getPointDashPattern(), 0);
  205. graphics.setStroke(dashed);
  206. }
  207. if (Objects.equals(shapeStyle.getPointType(), 1)) {
  208. //正方形
  209. graphics.drawRect(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight());
  210. if (BooleanUtil.isTrue(shapeStyle.getIsFill())) {
  211. graphics.fillRect(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight());
  212. }
  213. } else if (Objects.equals(shapeStyle.getPointType(), 2)) {
  214. //三角形
  215. int[] xPoints = {pxy[0], pxy[0] - shapeStyle.getHeight(), pxy[0] + shapeStyle.getHeight()};
  216. int[] yPoints = {pxy[1] - shapeStyle.getHeight(), pxy[1] + shapeStyle.getHeight(), pxy[1] + shapeStyle.getHeight()};
  217. int nPoints = 3; // 顶点数量
  218. graphics.drawPolygon(xPoints, yPoints, nPoints);
  219. if (BooleanUtil.isTrue(shapeStyle.getIsFill())) {
  220. graphics.fillPolygon(xPoints, yPoints, nPoints);
  221. }
  222. } else if (Objects.equals(shapeStyle.getPointType(), 3)) {
  223. //圆形
  224. graphics.drawOval(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight());
  225. if (BooleanUtil.isTrue(shapeStyle.getIsFill())) {
  226. graphics.fillOval(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight());
  227. }
  228. } else {
  229. //画一个简单的点
  230. graphics.drawArc(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight(), 0, 360);
  231. //是否填充
  232. if (BooleanUtil.isTrue(shapeStyle.getIsFill())) {
  233. graphics.fillArc(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight(), 0, 360);
  234. }
  235. }
  236. }
  237. public static List<Map<String, Object>> detectClick(String geoType,String bboxStr, List<Map<String, Object>> dataList, String shapeKey, List<LayerStyleView> layerStyleViews, int x, int y) {
  238. List<Map<String, Object>> clickSelectedDataList = new CopyOnWriteArrayList<>();
  239. BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
  240. String[] split = bboxStr.split(",");
  241. double[] bbox = new double[split.length];
  242. for (int i = 0; i < split.length; i++) {
  243. bbox[i] = Double.parseDouble(split[i]);
  244. }
  245. ShapeStyle shapeStyle = getShapeStyle(layerStyleViews,geoType);
  246. Graphics2D graphics = image.createGraphics();
  247. dataList.forEach(poi -> {
  248. Geometry geometry;
  249. try {
  250. geometry = WKT_READER.read(poi.get(shapeKey).toString());
  251. } catch (ParseException e) {
  252. throw new RuntimeException(e);
  253. }
  254. if (geometry != null) {
  255. drawGeometryShape(bbox, shapeStyle, graphics, poi, geometry);
  256. int rgb = image.getRGB(x, y);
  257. int alpha = (rgb >> 24) & 0xff; // 提取alpha值
  258. if (alpha > 0) {
  259. // 像素被填充
  260. clickSelectedDataList.add(poi);
  261. image.setRGB(x,y,0x00000000);
  262. }
  263. }
  264. });
  265. return clickSelectedDataList;
  266. }
  267. private static void drawGeometryShape(double[] bbox, ShapeStyle shapeStyle, Graphics2D graphics, Map<String, Object> poi, Geometry geometry) {
  268. if (StringUtils.equals(geometry.getGeometryType(), "Polygon")) {
  269. drawPolygonShape(bbox, graphics, geometry, poi, shapeStyle);
  270. } else if (StringUtils.equalsAny(geometry.getGeometryType(), "LinearRing", "LineString", "MultiLineString")) {
  271. drawLineShape(bbox, graphics, geometry, shapeStyle);
  272. } else if (StringUtils.equals(geometry.getGeometryType(), "Point")) {
  273. drawPointShape(bbox, graphics, geometry, poi, shapeStyle);
  274. }
  275. }
  276. public static void renderShape(BufferedImage image, String bboxStr, List<Map<String, Object>> dataList, ShapeStyle shapeStyle, String shapeKey) {
  277. String[] split = bboxStr.split(",");
  278. double[] bbox = new double[split.length];
  279. for (int i = 0; i < split.length; i++) {
  280. bbox[i] = Double.parseDouble(split[i]);
  281. }
  282. Graphics2D graphics = image.createGraphics();
  283. dataList.parallelStream().forEach(poi -> drawShape(shapeStyle, poi, bbox, graphics, shapeKey));
  284. }
  285. private static void drawShape(ShapeStyle shapeStyle, Map<String, Object> poi, double[] bbox, Graphics2D graphics, String shapeKey) {
  286. // Geometry shape = GeoUtils.getGeoUtils().createGeometry(poi.get(shapeKey));
  287. Geometry shape;
  288. try {
  289. shape = WKT_READER.read(poi.get(shapeKey).toString());
  290. } catch (ParseException e) {
  291. throw new RuntimeException(e);
  292. }
  293. if (shape != null) {
  294. drawGeometryShape(bbox, shapeStyle, graphics, poi, shape);
  295. }
  296. }
  297. public static Geometry convertWKBtoGeometry(byte[] wkbBytes) {
  298. WKBReader reader = new WKBReader();
  299. try {
  300. return reader.read(wkbBytes);
  301. } catch (ParseException e) {
  302. e.printStackTrace();
  303. return null;
  304. }
  305. }
  306. public static BufferedImage drawImage(String geoType,String bboxStr, List<Map<String, Object>> dataList, String shapeKey, List<LayerStyleView> layerStyleViews) {
  307. BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
  308. renderShape(image, bboxStr, dataList, getShapeStyle(layerStyleViews,geoType), shapeKey);
  309. return image;
  310. }
  311. public static ShapeStyle getShapeStyle(List<LayerStyleView> layerStyleViews,String geoType) {
  312. ShapeStyle shapeStyle = new ShapeStyle();
  313. for (LayerStyleView layerStyleView : layerStyleViews) {
  314. BeanUtils.copyProperties(layerStyleView, shapeStyle);
  315. setColorFromHex(layerStyleView.getColor(), shapeStyle::setColor);
  316. shapeStyle.setIsFill(Objects.equals(layerStyleView.getIsFill(), 1));
  317. setColorFromHex(layerStyleView.getBorderColor(), shapeStyle::setBorderColor);
  318. shapeStyle.setIsDashed(Objects.equals(layerStyleView.getIsDashed(), 1));
  319. setDashPattern(layerStyleView.getDashPattern(), shapeStyle::setDashPattern);
  320. setImage(layerStyleView.getBgImage(), shapeStyle::setBgImage);
  321. shapeStyle.setIsShowName(Objects.equals(layerStyleView.getIsShowName(), 1));
  322. setColorFromHex(layerStyleView.getFontColor(), shapeStyle::setFontColor);
  323. setImage(layerStyleView.getCenterPointImage(), shapeStyle::setCenterPointImage);
  324. setImage(layerStyleView.getPointMarkerImage(), shapeStyle::setPointMarkerImage);
  325. if (shapeStyle.getPointMarkerImage() != null) {
  326. if(StringUtils.equals(geoType,"1")){
  327. //如果类型为2图标要变大
  328. shapeStyle.setPointMarkerImageWidth(shapeStyle.getPointMarkerImageWidth()+10);
  329. shapeStyle.setPointMarkerImageHeight(shapeStyle.getPointMarkerImageHeight()+10);
  330. }
  331. shapeStyle.setPointMarkerImage(TileUtils.scaleWidthHeight(shapeStyle.getPointMarkerImage(), shapeStyle.getPointMarkerImageWidth(), shapeStyle.getPointMarkerImageHeight()));
  332. }
  333. }
  334. return shapeStyle;
  335. }
  336. }