Browse Source

瓦片生成

zhouyuexiang 1 year ago
parent
commit
9d4e0c3b48

+ 65 - 0
tile-service/src/main/java/cn/com/taiji/tile/style/ShapeStyle.java

@@ -0,0 +1,65 @@
+package cn.com.taiji.tile.style;
+
+import lombok.Data;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+/**
+ * @author zzyx 2024/1/5
+ */
+@Data
+public class ShapeStyle {
+    //颜色支持rgb rgba 如(255,0,255) ||(255, 0, 0, 128)
+    private Color color;
+    //是否填充或者只显示边框
+    private Boolean isFill;
+    //边框颜色
+    private Color borderColor=Color.RED;
+    //边框大小
+    private Integer borderSize=1;
+    //边框为虚线还是实现
+    private Boolean isDashed=false;
+    //虚线间隔 5像素实线,5像素空白  float[] dashPattern = {100, 10};
+    private float[] dashPattern;
+    //背景图片样式
+    private BufferedImage backgroundImage;
+    //是否显示中心点名称
+    private Boolean isShowName=false;
+    //字体颜色
+    private Color fontColor=Color.gray;
+    //字体大小
+    private Integer fontSize=1;
+    /**
+     * 中心点图片
+     */
+    private BufferedImage centerPointImage;
+
+    /**
+     * 点样式
+     */
+    private Integer pointSize;
+    private Color pointColor=Color.RED;
+    private Integer pointWidth=1;
+    private Integer pointHeight=1;
+    private Boolean pointFill;
+    private Boolean pointDashed;
+    private float[] pointDashPattern={2,2};
+    //形状 1:正方形,2:三角形,3:圆形
+    private Integer shapeType;
+    private BufferedImage pointMarkerImage;
+    private Integer pointMarkerImageWidth=13;
+    private Integer pointMarkerImageHeight=13;
+    private Boolean pointFontShow;
+    private Integer pointFontSize=12;
+    private Color pointFontColor=Color.BLACK;
+
+    //    线样式
+    private Color lineColor=Color.black;
+    //    线宽度
+    private Integer lineWidth=4;
+    //线性,1:实线,2:虚线,3:虚点线
+    private Integer lineType;
+    private float[] lineDashPattern={2,2};
+}
+

+ 255 - 0
tile-service/src/main/java/cn/com/taiji/tile/util/ShapeUtils.java

@@ -0,0 +1,255 @@
+package cn.com.taiji.tile.util;
+
+import cn.com.taiji.tile.model.POI;
+import cn.com.taiji.tile.style.ShapeStyle;
+import cn.hutool.core.util.BooleanUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @author zzyx 2024/1/5
+ */
+public class ShapeUtils {
+
+
+    public static final int WIDTH = 256;
+    public static final int HEIGHT = 256;
+
+    /**
+     * 获取瓦片字节数组
+     *
+     * @param bboxStr    逗号拼接的bbox字符串
+     * @param dataList   数据集合
+     * @param shapeStyle 样式
+     * @return 瓦片字节数组
+     */
+    public static byte[] getTileByte(String bboxStr, List<POI> dataList, ShapeStyle shapeStyle) {
+        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
+        String[] split = bboxStr.split(",");
+        double[] bbox = new double[split.length];
+        Graphics2D graphics = image.createGraphics();
+        for (POI poi : dataList) {
+            Geometry shape = GeoUtils.getGeoUtils().createGeometry(poi.getShape());
+            //判断数据点线面
+            if (StringUtils.equals(shape.getGeometryType(), "Polygon")) {
+                drawPolygonShape(bbox, graphics, shape, poi, shapeStyle);
+            } else if (StringUtils.equalsAny(shape.getGeometryType(), "LinearRing", "LineString", "MultiLineString")) {
+                drawLineShape(bbox, graphics, shape, shapeStyle);
+            } else if (StringUtils.equals(shape.getGeometryType(), "Point")) {
+                drawPointShape(bbox, graphics, shape, poi, shapeStyle);
+            }
+        }
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        try {
+            ImageIO.write(image, "png", buffer);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return buffer.toByteArray();
+    }
+
+
+    private static void drawPolygonShape(double[] bbox, Graphics2D graphics, Geometry geometry, POI poi, ShapeStyle shapeStyle) {
+        drawPolygonToTile(shapeStyle, geometry, graphics, bbox);
+        //是否需要设置字名称
+        if (BooleanUtil.isTrue(shapeStyle.getIsShowName()) && StringUtils.isNotBlank(poi.getName())) {
+            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.getName();
+            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.getLineColor());
+        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        if (Objects.equals(shapeStyle.getLineType(), 2)) {
+            graphics.setStroke(new BasicStroke(shapeStyle.getLineWidth(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, shapeStyle.getLineDashPattern(), 0.0f));
+        } else if (Objects.equals(shapeStyle.getLineType(), 3)) {
+            graphics.setStroke(new BasicStroke(shapeStyle.getLineWidth(), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, shapeStyle.getLineDashPattern(), 0.0f));
+        } else {
+            graphics.setStroke(new BasicStroke(shapeStyle.getLineWidth()));
+        }
+        //端点的样式可以是圆形 (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, POI poi, ShapeStyle shapeStyle) {
+        int[] pxy = TileUtils.toPixelXY(geometry.getCoordinate().x, geometry.getCoordinate().y, bbox[0], bbox[1], bbox[2], bbox[3]);
+        //设置边框 实现轮廓
+        graphics.setColor(shapeStyle.getPointColor());
+        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        //设置字体
+        //设置字体样式
+        if (BooleanUtil.isTrue(shapeStyle.getPointFontShow())) {
+            Font font = new Font("Arial", Font.BOLD, shapeStyle.getPointFontSize());
+            String s = poi.getName();
+            graphics.setColor(shapeStyle.getPointFontColor());
+            graphics.setFont(font);
+            graphics.drawString(s, pxy[0], pxy[1]);
+        }
+        BufferedImage pointMarkerImage = shapeStyle.getPointMarkerImage();
+        if (pointMarkerImage != null) {
+            graphics.drawImage(TileUtils.scaleWidthHeight(pointMarkerImage, shapeStyle.getPointWidth(), shapeStyle.getPointHeight()), 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() 方法将返回构成该多边形边界的所有顶点坐标。如果是一条线,它将返回线的所有顶点坐标。
+     * 这个方法在进行空间分析和几何处理时非常有用,比如当你需要遍历几何形状的所有顶点、计算形状的边界、或者在不同几何形状之间进行坐标转换时。
+     * <p>
+     * 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.getBackgroundImage();
+        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()));
+        }
+    }
+
+    /**
+     * Converts a hexadecimal color string to RGBA format.
+     *
+     * @param hexColor The hexadecimal color string (e.g., "#2860F1").
+     * @param alpha    The alpha value (0-255) for opacity.
+     * @return An array containing the RGBA values.
+     */
+    public static int[] hexToRgba(String hexColor, int alpha) {
+        // Remove the hash (#) sign if it exists
+        hexColor = hexColor.replace("#", "");
+
+        // Parse the string
+        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 the RGBA values
+        return new int[]{red, green, blue, alpha};
+    }
+
+    private static void drawPointShape(ShapeStyle shapeStyle, Graphics2D graphics, int[] pxy) {
+        if (BooleanUtil.isTrue(shapeStyle.getPointDashed())) {
+            //如果是
+            Stroke dashed = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, shapeStyle.getPointDashPattern(), 0);
+            graphics.setStroke(dashed);
+        }
+        if (Objects.equals(shapeStyle.getShapeType(), 1)) {
+            //正方形
+            graphics.drawRect(pxy[0], pxy[1], shapeStyle.getPointWidth(), shapeStyle.getPointHeight());
+            if (BooleanUtil.isTrue(shapeStyle.getPointFill())) {
+                graphics.fillRect(pxy[0], pxy[1], shapeStyle.getPointWidth(), shapeStyle.getPointHeight());
+            }
+        } else if (Objects.equals(shapeStyle.getShapeType(), 2)) {
+            //三角形
+            int[] xPoints = {pxy[0], pxy[0] - shapeStyle.getPointHeight(), pxy[0] + shapeStyle.getPointHeight()};
+            int[] yPoints = {pxy[1] - shapeStyle.getPointHeight(), pxy[1] + shapeStyle.getPointHeight(), pxy[1] + shapeStyle.getPointHeight()};
+            int nPoints = 3; // 顶点数量
+            graphics.drawPolygon(xPoints, yPoints, nPoints);
+            if (BooleanUtil.isTrue(shapeStyle.getPointFill())) {
+                graphics.fillPolygon(xPoints, yPoints, nPoints);
+            }
+        } else if (Objects.equals(shapeStyle.getShapeType(), 3)) {
+            //圆形
+            graphics.drawOval(pxy[0], pxy[1], shapeStyle.getPointWidth(), shapeStyle.getPointHeight());
+            if (BooleanUtil.isTrue(shapeStyle.getPointFill())) {
+                graphics.fillOval(pxy[0], pxy[1], shapeStyle.getPointWidth(), shapeStyle.getPointHeight());
+            }
+        } else {
+            //画一个简单的点
+            graphics.drawArc(pxy[0], pxy[1], shapeStyle.getPointWidth(), shapeStyle.getPointHeight(), 0, 360);
+            //是否填充
+            if (BooleanUtil.isTrue(shapeStyle.getPointFill())) {
+                graphics.fillArc(pxy[0], pxy[1], shapeStyle.getPointWidth(), shapeStyle.getPointHeight(), 0, 360);
+            }
+        }
+    }
+
+
+}