GIS Java 生成四至图

发布于:2024-06-01 ⋅ 阅读:(182) ⋅ 点赞:(0)

目录

前言

操作步骤:

1,求出多边形的四至点

2,下载地图

3,绘制多边形


前言

对于地图上的一个多边形地块,其四至图就是能够覆盖这个多边形的最小矩形,也就是求出这个多边形的最东点,最西点,最南点,最北点,即四至点,其中最西点和最北点构成左上角,最南点和最东点构成右下点,只要知道了左上角和右下角,就知道这个最小覆盖矩形了。

前端生成四至图是比较简单的,后端如何生成四至图呢?后端生成四至图得到的是一个图片,图片的底图是天地图,在这个底图之上就是多边形。多边形的坐标长这样:"[[31.21729,121.583274],[31.217096,121.58378],[31.216985,121.583457],[31.21729,121.583274]]",它是4326的坐标系,第一个坐标和最后一个坐标是同一个,首尾相连,下图就是它的四至图。

操作步骤:

1,求出多边形的四至点

        即多边形的最东点,最西点,最南点,最北点,然后我们就知道四至图的左上点和右下点这两个点的地理坐标,根据这两个点求出它们各自在天地图的瓦片位置 (x,y) 。

/**
 * 计算瓦片行列号
 * @param latitude  纬度
 * @param longitude 经度
 * @param zoom      缩放级别
 * @return 瓦片行列号数组,格式为 [col, row]
 */
public static int[] calculateTileXY(double latitude, double longitude, int zoom) {
    double n = Math.pow(2, zoom);
    double mercX = (longitude + 180) / 360;
    double mercY = (1 - Math.log(Math.tan(Math.toRadians(latitude)) + 1 / Math.cos(Math.toRadians(latitude))) / Math.PI) / 2;

    int tileX = (int) Math.floor(mercX * n);
    int tileY = (int) Math.floor(mercY * n);

    return new int[]{tileX, tileY};
}

2,下载地图

        下载从左上点到右下点的所有小瓦片图片,并把这些小瓦片地图图片合并为一张完整的大地图图片,这些小瓦片地图的名称依次命名为 1.png,2.png....。下载次序是从左上角开始从左到右,从上到下依次生成图片。记得下载的时候需要有天地图的秘钥 tk 。

int k = 1;
// 从左上角开始从左到右,从上到下依次生成图片,图片序号依次递增
for (int j = leftUp[1]; j <= rightDown[1]; j++) {
    for (int i = leftUp[0]; i <= rightDown[0]; i++) {
        System.out.println("/" + j + "/" + i);
        HttpOkUtil.downloadImg("http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile" +
                "&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX=" + zoom +
                "&TILEROW=" + j + "&TILECOL=" + i + "&tk=xx", pathParent + k + ".png");
        k++;
    }
}

 下载得到的小瓦片大概长这样

 

 融合地图代码:

    /**
     * 合并瓦片图片
     *
     * @param tiles           包含所有瓦片及其行列信息的列表
     * @param outputImagePath 输出合并后地图的文件路径
     * @throws IOException 如果图像文件读取失败或写入失败
     */
    public static void mergeTiles(List<BufferedImage> tiles, String outputImagePath, int widthCnt, int heightCnt) throws IOException {
        // 假设所有瓦片都是正方形,获取瓦片的宽度和高度
        int tileWidth = tiles.get(0).getWidth();
        int tileHeight = tiles.get(0).getHeight();

        // 计算合并后图像的尺寸
        int width = tileWidth * widthCnt;// (int) Math.ceil(Math.sqrt(tiles.size())) * tileWidth;
        int height = tileHeight * heightCnt;// (int) Math.ceil(Math.sqrt(tiles.size())) * tileHeight;// width; //
        // 假设合并后的地图是正方形

        // 创建一个新的图像,用于合并后的地图
        BufferedImage mergedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

        // 使用Graphics2D来绘制图像
        Graphics2D g2d = mergedImage.createGraphics();

        // 绘制瓦片到新图像上
        int x = 0;
        int y = 0;
        for (BufferedImage tile : tiles) {
            g2d.drawImage(tile, x, y, null);
            x += tileWidth;
            if (x >= width) {
                x = 0;
                y += tileHeight;
            }
        }

        // 释放Graphics2D资源
        g2d.dispose();

        // 将合并后的图像写入文件
        ImageIO.write(mergedImage, "PNG", new File(outputImagePath));
    }

3,绘制多边形

        在这张融合好的大地图图片上绘制多边形和四至矩形图,因为大地图图片和小瓦片是 png 格式,没有地图坐标,所有要把 path 中的点相对于其在瓦片中的位置按比例进行计算。因为每个小瓦片都是 256*256 的像素格式,所以大地图就是 (256*width,256*height) ,以大地图的左上点为相对起始点开始绘画,可以精确到像素级。

// boundingBox 就是四至图,有左上,右下两个点,也就是西北点,东南点,即west,east,north,south这四个属性
for (GeoPoint point : list) {
    int[] pointXy = FourBoundariesGraphUtil.calculateTileXY(point.getY(), point.getX(), zoom);
    FourBoundariesGraphUtil.BoundingBox tmp = FourBoundariesGraphUtil.tile2boundingBox(pointXy[0], pointXy[1], zoom);
    res.add((pointXy[0] - originX) * 256 + (int) ((point.getX() - tmp.west) / (tmp.east - tmp.west) * 256));
    res.add((pointXy[1] - originY) * 256 + (int) ((tmp.north - point.getY()) / (tmp.north - tmp.south) * 256));
}

public static class BoundingBox {
        double north;
        double south;
        double east;
        double west;

        public double[] get_tianditu_info() {
            return new double[]{west, north, east, south};
        }
    }

    public static BoundingBox tile2boundingBox(final int x, final int y, final int zoom) {
        BoundingBox bb = new BoundingBox();
        bb.north = tile2lat(y, zoom);
        bb.south = tile2lat(y + 1, zoom);
        bb.west = tile2lon(x, zoom);
        bb.east = tile2lon(x + 1, zoom);
        return bb;
    }

    public static double tile2lon(int x, int z) {
        return x / Math.pow(2.0, z) * 360.0 - 180;
    }

    public static double tile2lat(int y, int z) {
        double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, z);
        return Math.toDegrees(Math.atan(Math.sinh(n)));
    }
/**
 * 计算瓦片行列号
 * @param latitude  纬度
 * @param longitude 经度
 * @param zoom      缩放级别
 * @return 瓦片行列号数组,格式为 [col, row]
 */
public static int[] calculateTileXY(double latitude, double longitude, int zoom) {
    double n = Math.pow(2, zoom);
    double mercX = (longitude + 180) / 360;
    double mercY = (1 - Math.log(Math.tan(Math.toRadians(latitude)) + 1 / Math.cos(Math.toRadians(latitude))) / Math.PI) / 2;

    int tileX = (int) Math.floor(mercX * n);
    int tileY = (int) Math.floor(mercY * n);

    return new int[]{tileX, tileY};
}

把得到的坐标点绘制到图片,这个需要使用 Java Image 的 api,百度一下就知道了。如果需要在大地图图片里写其他的信息但发现大地图图片生成得有些小了,那就在下载瓦片的地方减小左上角或增大右下角的位置,多留一点空间,如果有其他的要求,也可以对生成好的地图图片进行裁剪。下载的地图可以是任意级别经度,只要天地图支持,默认是18级最大精度。