package cn.com.taiji.tile.util; import cn.com.taiji.common.model.LayerStyleView; import cn.com.taiji.tile.style.ShapeStyle; import cn.hutool.core.util.BooleanUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.ParseException; import org.locationtech.jts.io.WKBReader; import org.locationtech.jts.io.WKTReader; import org.locationtech.jts.io.WKTWriter; import org.springframework.beans.BeanUtils; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.URL; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; /** * @author zzyx 2024/1/5 */ @Slf4j public class ShapeUtils { public static final int WIDTH = 256; public static final int HEIGHT = 256; public final static WKTReader WKT_READER = new WKTReader(); private static void setImage(String imageUrl, Consumer imageConsumer) { if (StringUtils.isNotBlank(imageUrl)) { try { URL url = new URL(imageUrl); BufferedImage image = ImageIO.read(url); imageConsumer.accept(image); } catch (IOException e) { log.error("Error loading image from URL: {}", imageUrl, e); } } } private static void setColorFromHex(String hexColor, Consumer colorConsumer) { if (StringUtils.isNotBlank(hexColor)) { try { int[] rgba = hexToRgba(hexColor, 255); colorConsumer.accept(new Color(rgba[0], rgba[1], rgba[2], rgba[3])); } catch (IllegalArgumentException e) { log.error("Invalid hex color format: {}", hexColor, e); } } } private static void setDashPattern(String dashPatternStr, Consumer dashPatternConsumer) { if (StringUtils.isNotBlank(dashPatternStr)) { String[] split = dashPatternStr.split(","); float[] dashPatternArr = new float[split.length]; for (int i = 0; i < split.length; i++) { dashPatternArr[i] = Float.parseFloat(split[i]); } dashPatternConsumer.accept(dashPatternArr); } } private static void drawPolygonShape(double[] bbox, Graphics2D graphics, Geometry geometry, Map poi, ShapeStyle shapeStyle) { drawPolygonToTile(shapeStyle, geometry, graphics, bbox); //是否需要设置字名称 if (BooleanUtil.isTrue(shapeStyle.getIsShowName()) && StringUtils.isNotBlank(poi.get("name").toString())) { org.locationtech.jts.geom.Point centroid = geometry.getCentroid(); int[] pxy = TileUtils.toPixelXY(centroid.getX(), centroid.getY(), bbox[0], bbox[1], bbox[2], bbox[3]); //设置字体样式 Font font = new Font("Arial", Font.TRUETYPE_FONT, shapeStyle.getFontSize()); String s = poi.get("name").toString(); graphics.setColor(shapeStyle.getFontColor()); graphics.setFont(font); graphics.drawString(s, pxy[0], pxy[1]); //设置面的图标 BufferedImage cpi = shapeStyle.getCenterPointImage(); if (cpi != null) { graphics.drawImage(TileUtils.scaleWidthHeight(cpi, 13, 13), pxy[0], pxy[1], null); } } } public static void drawLineShape(double[] bbox, Graphics2D graphics, Geometry geometry, ShapeStyle shapeStyle) { int[] xpoint = new int[(geometry.getCoordinates()).length]; int[] ypoint = new int[(geometry.getCoordinates()).length]; convertCoordinatesToPixels(geometry.getCoordinates(), xpoint, ypoint, bbox); graphics.setColor(shapeStyle.getColor()); graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (Objects.equals(shapeStyle.getLineType(), 2)) { graphics.setStroke(new BasicStroke(shapeStyle.getWidth(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, shapeStyle.getDashPattern(), 0.0f)); } else if (Objects.equals(shapeStyle.getLineType(), 3)) { graphics.setStroke(new BasicStroke(shapeStyle.getWidth(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, shapeStyle.getDashPattern(), 0.0f)); } else { graphics.setStroke(new BasicStroke(shapeStyle.getWidth())); } //端点的样式可以是圆形 (CAP_ROUND)、平直 (CAP_BUTT) 或方形 (CAP_SQUARE) // 连接样式:线段之间的连接可以设置为斜接 (JOIN_MITER)、圆角 (JOIN_ROUND) 或平直 (JOIN_BEVEL)。 // graphics.setStroke(new BasicStroke(10f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)); graphics.drawPolyline(xpoint, ypoint, geometry.getCoordinates().length); } public static void drawPointShape(double[] bbox, Graphics2D graphics, Geometry geometry, Map poi, ShapeStyle shapeStyle) { int[] pxy = TileUtils.toPixelXY(geometry.getCoordinate().x, geometry.getCoordinate().y, bbox[0], bbox[1], bbox[2], bbox[3]); // int dx = (int) (256.0D * (geometry.getCoordinate().x - bbox[0]) / (bbox[2] - bbox[0])); // int dy = 256 - (int) (256.0D * (geometry.getCoordinate().y - bbox[1]) / (bbox[3] - bbox[1])); // int[] pxy = new int[]{dx-10, dy-10}; //设置边框 实现轮廓 graphics.setColor(shapeStyle.getColor()); graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //设置字体 //设置字体样式 if (BooleanUtil.isTrue(shapeStyle.getIsShowName()) && StringUtils.isNotBlank(poi.get("name").toString())) { Font font = new Font("Arial", Font.BOLD, shapeStyle.getFontSize()); String s = poi.get("name").toString(); graphics.setColor(shapeStyle.getFontColor()); graphics.setFont(font); graphics.drawString(s, pxy[0], pxy[1]); } BufferedImage pointMarkerImage = shapeStyle.getPointMarkerImage(); if (pointMarkerImage != null) { graphics.drawImage(pointMarkerImage, pxy[0], pxy[1], null); } else { drawPointShape(shapeStyle, graphics, pxy); } } public static void convertCoordinatesToPixels(Coordinate[] coordinates, int[] xPoints, int[] yPoints, double[] bbox) { // 坐标转换逻辑 for (int i = 0; i < coordinates.length; i++) { Coordinate c = coordinates[i]; //坐标转换:对于每个坐标 c,代码调用 TileUtils.toPixelXY 方法,将地理坐标(经纬度)转换为像素坐标。这个转换考虑了一个称为 bbox(边界框)的地理区域,这个区域定义了地理坐标到像素坐标转换的上下文。bbox 通常包含边界框的最小经度、最小纬度、最大经度和最大纬度。 int[] pxy = TileUtils.toPixelXY(c.getX(), c.getY(), bbox[0], bbox[1], bbox[2], bbox[3]); xPoints[i] = pxy[0]; yPoints[i] = pxy[1]; } } /** * 面填充样式 * 在地理信息系统(GIS)和空间数据处理领域中,Geometry 类是一个常用的概念,用于表示空间对象的形状和位置。这个类通常包含各种方法来操作和查询几何形状。getCoordinates() 方法是 Geometry 类中的一个方法,它的作用是获取构成该几何形状的所有坐标点。 * 当你调用 Geometry 对象的 getCoordinates() 方法时,它会返回一个坐标数组,这个数组包含了组成该几何形状的所有顶点坐标。这些坐标通常是 Coordinate 对象,每个 Coordinate 包含 x(经度)、y(纬度)和可选的 z(高度)值。 * 例如,如果你的 Geometry 对象是一个多边形,getCoordinates() 方法将返回构成该多边形边界的所有顶点坐标。如果是一条线,它将返回线的所有顶点坐标。 * 这个方法在进行空间分析和几何处理时非常有用,比如当你需要遍历几何形状的所有顶点、计算形状的边界、或者在不同几何形状之间进行坐标转换时。 *

* Coordinate 是在地理信息系统(GIS)和计算机图形学中常用的一个基本概念,用于表示空间中的点。在多种编程环境和库中,Coordinate 类或结构体通常用于存储和操作这些点的位置信息。其主要作用和特点包括: * 存储位置信息:Coordinate 通常包含至少两个维度的数据,即 x(经度)和 y(纬度)。在三维空间中,它还可以包含 z(高程或深度)值。 * 空间分析和计算:在 GIS 和计算机图形学应用中,Coordinate 用于执行各种空间分析和计算,如测量距离、定义几何形状(如线和多边形)的边界等。 * 数据转换和处理:Coordinate 在进行坐标系转换、地图投影、空间查询等操作中也非常重要。 * 可视化和绘图:在地图制作和空间数据可视化中,Coordinate 用于确定应在地图上绘制的点的确切位置。 * 在不同的编程库和框架中,Coordinate 类可能还包含了其他辅助方法,如计算两个坐标之间的距离、比较坐标点是否相等等。例如,在 Java 的 JTS Topology Suite(一个常用的空间数据处理库)中,Coordinate 类就提供了这样的功能。 */ public static void drawPolygonToTile(ShapeStyle shapeStyle, Geometry shape, Graphics2D graphics, double[] bbox) { //普通面 if (shape != null && shape.getCoordinates().length > 0) { int[] xpoint = new int[(shape.getCoordinates()).length]; int[] ypoint = new int[(shape.getCoordinates()).length]; convertCoordinatesToPixels(shape.getCoordinates(), xpoint, ypoint, bbox); //设置样式 //2.设置抗锯齿 graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //是否填充 if (BooleanUtil.isTrue(shapeStyle.getIsFill())) { //fill 是将设置的颜色全部填充 graphics.setColor(shapeStyle.getColor()); //设置背景面 setBackgroundImage(graphics, shapeStyle); Polygon polygon = new Polygon(xpoint, ypoint, shape.getNumPoints()); //填充 graphics.fill(polygon); //设置边框颜色 setBorderStyle(graphics, shapeStyle); //drawPolygon 是透明的只会出来边框 graphics.drawPolygon(polygon); } else { setBorderStyle(graphics, shapeStyle); graphics.drawPolygon(xpoint, ypoint, (shape.getCoordinates()).length); } } } private static void setBackgroundImage(Graphics2D graphics, ShapeStyle shapeStyle) { BufferedImage backgroundImage = shapeStyle.getBgImage(); if (backgroundImage != null) { TexturePaint texturePaint = new TexturePaint(backgroundImage, new Rectangle(0, 0, backgroundImage.getWidth(), backgroundImage.getHeight())); graphics.setPaint(texturePaint); } } private static void setBorderStyle(Graphics2D graphics, ShapeStyle shapeStyle) { //不填充只显示边框颜色 graphics.setColor(shapeStyle.getBorderColor()); // 设置虚线:5像素实线,5像素空白 if (BooleanUtil.isTrue(shapeStyle.getIsDashed())) { Stroke dashed = new BasicStroke(shapeStyle.getBorderSize(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, shapeStyle.getDashPattern(), 0); graphics.setStroke(dashed); } else { graphics.setStroke(new BasicStroke(shapeStyle.getBorderSize())); } } public static int[] hexToRgba(String hexColor, int alpha) { hexColor = hexColor.replace("#", ""); int red = Integer.valueOf(hexColor.substring(0, 2), 16); int green = Integer.valueOf(hexColor.substring(2, 4), 16); int blue = Integer.valueOf(hexColor.substring(4, 6), 16); return new int[]{red, green, blue, alpha}; } private static void drawPointShape(ShapeStyle shapeStyle, Graphics2D graphics, int[] pxy) { if (BooleanUtil.isTrue(shapeStyle.getIsDashed())) { //如果是 Stroke dashed = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, shapeStyle.getPointDashPattern(), 0); graphics.setStroke(dashed); } if (Objects.equals(shapeStyle.getPointType(), 1)) { //正方形 graphics.drawRect(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight()); if (BooleanUtil.isTrue(shapeStyle.getIsFill())) { graphics.fillRect(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight()); } } else if (Objects.equals(shapeStyle.getPointType(), 2)) { //三角形 int[] xPoints = {pxy[0], pxy[0] - shapeStyle.getHeight(), pxy[0] + shapeStyle.getHeight()}; int[] yPoints = {pxy[1] - shapeStyle.getHeight(), pxy[1] + shapeStyle.getHeight(), pxy[1] + shapeStyle.getHeight()}; int nPoints = 3; // 顶点数量 graphics.drawPolygon(xPoints, yPoints, nPoints); if (BooleanUtil.isTrue(shapeStyle.getIsFill())) { graphics.fillPolygon(xPoints, yPoints, nPoints); } } else if (Objects.equals(shapeStyle.getPointType(), 3)) { //圆形 graphics.drawOval(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight()); if (BooleanUtil.isTrue(shapeStyle.getIsFill())) { graphics.fillOval(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight()); } } else { //画一个简单的点 graphics.drawArc(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight(), 0, 360); //是否填充 if (BooleanUtil.isTrue(shapeStyle.getIsFill())) { graphics.fillArc(pxy[0], pxy[1], shapeStyle.getWidth(), shapeStyle.getHeight(), 0, 360); } } } public static List> detectClick(String geoType,String bboxStr, List> dataList, String shapeKey, List layerStyleViews, int x, int y) { List> clickSelectedDataList = new CopyOnWriteArrayList<>(); BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); String[] split = bboxStr.split(","); double[] bbox = new double[split.length]; for (int i = 0; i < split.length; i++) { bbox[i] = Double.parseDouble(split[i]); } ShapeStyle shapeStyle = getShapeStyle(layerStyleViews,geoType); Graphics2D graphics = image.createGraphics(); dataList.forEach(poi -> { Geometry geometry; try { geometry = WKT_READER.read(poi.get(shapeKey).toString()); } catch (ParseException e) { throw new RuntimeException(e); } if (geometry != null) { drawGeometryShape(bbox, shapeStyle, graphics, poi, geometry); int rgb = image.getRGB(x, y); int alpha = (rgb >> 24) & 0xff; // 提取alpha值 if (alpha > 0) { // 像素被填充 clickSelectedDataList.add(poi); image.setRGB(x,y,0x00000000); } } }); return clickSelectedDataList; } private static void drawGeometryShape(double[] bbox, ShapeStyle shapeStyle, Graphics2D graphics, Map poi, Geometry geometry) { if (StringUtils.equals(geometry.getGeometryType(), "Polygon")) { drawPolygonShape(bbox, graphics, geometry, poi, shapeStyle); } else if (StringUtils.equalsAny(geometry.getGeometryType(), "LinearRing", "LineString", "MultiLineString")) { drawLineShape(bbox, graphics, geometry, shapeStyle); } else if (StringUtils.equals(geometry.getGeometryType(), "Point")) { drawPointShape(bbox, graphics, geometry, poi, shapeStyle); } } public static void renderShape(BufferedImage image, String bboxStr, List> dataList, ShapeStyle shapeStyle, String shapeKey) { String[] split = bboxStr.split(","); double[] bbox = new double[split.length]; for (int i = 0; i < split.length; i++) { bbox[i] = Double.parseDouble(split[i]); } Graphics2D graphics = image.createGraphics(); dataList.parallelStream().forEach(poi -> drawShape(shapeStyle, poi, bbox, graphics, shapeKey)); } private static void drawShape(ShapeStyle shapeStyle, Map poi, double[] bbox, Graphics2D graphics, String shapeKey) { // Geometry shape = GeoUtils.getGeoUtils().createGeometry(poi.get(shapeKey)); Geometry shape; try { shape = WKT_READER.read(poi.get(shapeKey).toString()); } catch (ParseException e) { throw new RuntimeException(e); } if (shape != null) { drawGeometryShape(bbox, shapeStyle, graphics, poi, shape); } } public static Geometry convertWKBtoGeometry(byte[] wkbBytes) { WKBReader reader = new WKBReader(); try { return reader.read(wkbBytes); } catch (ParseException e) { e.printStackTrace(); return null; } } public static BufferedImage drawImage(String geoType,String bboxStr, List> dataList, String shapeKey, List layerStyleViews) { BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); renderShape(image, bboxStr, dataList, getShapeStyle(layerStyleViews,geoType), shapeKey); return image; } public static ShapeStyle getShapeStyle(List layerStyleViews,String geoType) { ShapeStyle shapeStyle = new ShapeStyle(); for (LayerStyleView layerStyleView : layerStyleViews) { BeanUtils.copyProperties(layerStyleView, shapeStyle); setColorFromHex(layerStyleView.getColor(), shapeStyle::setColor); shapeStyle.setIsFill(Objects.equals(layerStyleView.getIsFill(), 1)); setColorFromHex(layerStyleView.getBorderColor(), shapeStyle::setBorderColor); shapeStyle.setIsDashed(Objects.equals(layerStyleView.getIsDashed(), 1)); setDashPattern(layerStyleView.getDashPattern(), shapeStyle::setDashPattern); setImage(layerStyleView.getBgImage(), shapeStyle::setBgImage); shapeStyle.setIsShowName(Objects.equals(layerStyleView.getIsShowName(), 1)); setColorFromHex(layerStyleView.getFontColor(), shapeStyle::setFontColor); setImage(layerStyleView.getCenterPointImage(), shapeStyle::setCenterPointImage); setImage(layerStyleView.getPointMarkerImage(), shapeStyle::setPointMarkerImage); if (shapeStyle.getPointMarkerImage() != null) { if(StringUtils.equals(geoType,"1")){ //如果类型为2图标要变大 shapeStyle.setPointMarkerImageWidth(shapeStyle.getPointMarkerImageWidth()+10); shapeStyle.setPointMarkerImageHeight(shapeStyle.getPointMarkerImageHeight()+10); } shapeStyle.setPointMarkerImage(TileUtils.scaleWidthHeight(shapeStyle.getPointMarkerImage(), shapeStyle.getPointMarkerImageWidth(), shapeStyle.getPointMarkerImageHeight())); } } return shapeStyle; } }