前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自定义瓦片地图切图-基于腾讯地图

自定义瓦片地图切图-基于腾讯地图

原创
作者头像
bug专8
修改2021-04-28 15:07:46
5.2K3
修改2021-04-28 15:07:46
举报
文章被收录于专栏:Java菜鸡冲冲冲!

1、需求

在腾讯地图上发一张自定义的手绘地图,由于手绘地图像素都比较高,加载一整张图速度极慢。将手绘地图按照地图的瓦片规则切片分开加载。

2、瓦片地图简介

https://baike.baidu.com/item/%E7%93%A6%E7%89%87%E5%9C%B0%E5%9B%BE/8006049?fr=aladdin

3、腾讯叠加自定义瓦片api

https://lbs.qq.com/webDemoCenter/javascriptV2/mapType/mapOverlayImage

4、切瓦片图思路

  1. 上传手绘地图源图;
  2. 根据坐标定点对源图进行拉升(由于源图在地图上覆盖的时候进行了微调),得到拉升源图;
  3. 对拉升后的源图进行四周像素填充,生成符合瓦片图格式的切割源图(由于切割源图尺寸较大,只记录尺寸,未真正生成切割源图,避免服务器内存溢出);
  4. 根据上一步记录的切割源图参数信息,将拉升源图切割成瓦片源图,并进行四周像素填充,得到符合规格的瓦片源图;
  5. 用循环对瓦片源图进行逐级切割,并转换统一的瓦片格式(256*256);

5、遇到的问题及优化过程

1、怎么根据首尾坐标对源图进行拉伸

解:根据源图的首尾坐标反向计算拉升后图片的宽高比例,通过比例将源图的尺寸进行拉升(只增大尺寸,不压缩尺寸)。

2、根据切割源图切图后和原图位置有偏移

解:合成的切割源图的尺寸(长、宽)必须是瓦片切图个数(横向、纵向)的倍数,因为在切割瓦片时的尺寸都是整数。所以在合成切割源图时,如果尺寸不是瓦片切图个数的倍数,需要将图片尺寸放大为最临近的一个倍数值。

3、切图速度过慢

解:切图过程中存在很多空白透明图,无需切割。在切割前先判断对应瓦片尺寸中的图是否为全空白图,如果是全空白图则不需要切图处理。

4、服务器切图内存溢出

4.1、手绘源图尺寸太大,比如超过50m时会存在服务器内存不够

解:适当降低源图尺寸,提高服务器的内存配置。

4.1、通过源图生成的切割源图尺寸过大

解:这儿主要是生成的切割源图尺寸较大,只需要记录切割源图的参数信息,不真正生成切割源图。然后根据切割源图的参数 信息,对拉升源图进行切割填充,生成指定层级的瓦片源图。然后循环对瓦片源图进行切割得到瓦片图。

5、瓦片图存在锯齿

解:

第一种:提高源图的像素质量。

第二种:Graphics2D在画图前进行缩放设置

代码语言:java
复制
/*
 * java提供了4个缩放的微调选项
 * image.SCALE_SMOOTH //平滑优先(图片质量好、切割速度慢)
 * image.SCALE_FAST //速度优先(图片质量差、切割速度快)
 * image.SCALE_AREA_AVERAGING //区域均值(图片质量好、切割速度慢)
 * image.SCALE_REPLICATE //像素复制型缩放(图片质量差、切割速度快)
 * image.SCALE_DEFAULT //默认缩放模式(图片质量差、切割速度快)
*/
Image image = bi.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH);

6、切图层级小于图片所在的理论层级时,合成的切割源图较大导致内存溢出

解:当切图层级小于图片所在的理论层级时,以理论层级为准。

6、参考文档连接

http://www.what21.com/sys/view/java_java-gui_1456896004842.html

https://blog.csdn.net/love_QJP/article/details/81475055

https://blog.csdn.net/mygisforum/article/details/22997879

https://blog.csdn.net/wanm9/article/details/52319061

https://blog.csdn.net/iteye_18766/article/details/82642863

https://blog.csdn.net/iteye_12605/article/details/82240200

7、PictureCutUtil工具封装

代码语言:javascript
复制
package com.example.picturecut.util;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.CropImageFilter;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static com.sun.org.apache.xalan.internal.lib.ExsltMath.power;

/**
 * 腾讯地图叠加自定义瓦片tile map手绘图处理
 * @author jdp
 */
public class PictureCutUtil {

    /**
     * 腾讯地图-自定义瓦片图切图
     * @param fromFile      源文件
     * @param tempFilePath  临时文件路径
     * @param group         切图分组(唯一)
     * @param lonStart      开始经度
     * @param latStart      开始纬度
     * @param lonEnd        结束经度
     * @param latEnd        结束纬度
     * @param cutStartLevel 切片层级开始
     * @param cutStartLevel 切片层级结束
     */
    public static void tencentPictureCut(File fromFile, String tempFilePath, String group, double lonStart, double latStart, double lonEnd, double latEnd, Integer cutStartLevel, Integer cutEndLevel){
        String toFilePath = tempFilePath + "/" + group;
        String cutSrcBasePath = tempFilePath + "/" + group +"/cutsrc";
        File toFile = new File(toFilePath + "/cutsrc.png");
        // 瓦片单元格大小px(默认256)
        int cellPixel = 256;
        // 切图层级范围(默认14~18级),切图的层级必须<=切图所在的理论层级
        if(cutStartLevel==null || cutStartLevel <= 0){
            cutStartLevel = 14;
        }
        if(cutEndLevel==null || cutEndLevel <= 0){
            cutEndLevel = 18;
        }
        // 通过首尾坐标合成拉伸后的图片
        Map<String, Object> offsetMap = getOffset(lonStart, latStart, lonEnd, latEnd,cellPixel);
        // 计算实际偏移尺寸
        // 源图参数(切图理论层级)
        double offsetTop = Double.valueOf(offsetMap.get("top").toString());
        double offsetRight = Double.valueOf(offsetMap.get("right").toString());
        double offsetBottom = Double.valueOf(offsetMap.get("bottom").toString());
        double offsetLeft = Double.valueOf(offsetMap.get("left").toString());
        int level = Integer.valueOf(offsetMap.get("level").toString());
        // 如果理论层级大于切图层级,则从理论层级开始切图
        if(level > cutStartLevel){
            cutStartLevel = level;
        }
        if(cutStartLevel > cutEndLevel){
            cutEndLevel = cutStartLevel;
        }
        // 合成拉伸后的源图
        Map<String, Object> cutSrcMap = composeCutSrcPng(fromFile,toFile,offsetTop,offsetRight,offsetBottom,offsetLeft,cellPixel,level);
        // 拉伸后源图参数(切图理论层级)
        int cellSize = Integer.valueOf(cutSrcMap.get("standardSize").toString())/(1<<(cutStartLevel-level));
        int realTop = Integer.valueOf(cutSrcMap.get("realTop").toString()) % cellSize;
        int realRight = Integer.valueOf(cutSrcMap.get("realRight").toString()) % cellSize;
        int realBottom = Integer.valueOf(cutSrcMap.get("realBottom").toString()) % cellSize;
        int realLeft = Integer.valueOf(cutSrcMap.get("realLeft").toString()) % cellSize;
        // 源图瓦片参数(切图开始层级)
        int[] start = LonLatToXYZ(lonStart, latStart, cutStartLevel);
        int[] end = LonLatToXYZ(lonEnd, latEnd, cutStartLevel);
        // 源图切图高度范围
        int heightStart = 0;
        int heightEnd = 0;
        for (int y = start[1]; y <= end[1]; y++) {
            // 源图切图宽度范围
            int widthStart = 0;
            int widthEnd = 0;
            // 计算每个单元偏移量
            int cellTop = 0;
            int cellBottom = 0;
            // 上边
            if(y == start[1]){
                heightEnd = cellSize - realTop;
                cellTop = realTop;
            }
            // 下右边
            else if(y == end[1]){
                heightEnd = heightStart + (cellSize - realBottom);
                cellBottom = realBottom;
            }
            // 中间部分
            else{
                heightEnd = heightStart + cellSize;
            }
            for (int x = start[0]; x <= end[0]; x++) {
                int cellRight = 0;
                int cellLeft = 0;
                // 左边
                if(x == start[0]){
                    widthEnd = cellSize - realLeft;
                    cellLeft = realLeft;
                }
                // 右边
                else if(x == end[0]){
                    widthEnd = widthStart + (cellSize - realRight);
                    cellRight = realRight;
                }
                // 中间部分
                else{
                    widthEnd = widthStart + cellSize;
                }
                // 源图切割
                String cutSrcFilePath = cutSrcBasePath + "/" + cutStartLevel + "-" + x + "-" + y + ".png";
                File cutSrcFile = new File(cutSrcFilePath);
                srcPictureCut(toFile,cutSrcFilePath,widthStart,heightStart,widthEnd,heightEnd);
                // 源图像素填充
                resizePng(cutSrcFile,cutSrcFile,0,0,cellTop,cellRight,cellBottom,cellLeft,false);
                // 根据源图切割瓦片图
                pictureCut(cutSrcFile,toFilePath,256,256,cutStartLevel,cutStartLevel,cutEndLevel,x,y);

                widthStart += widthEnd - widthStart;
            }
            heightStart += heightEnd - heightStart;
        }
        // 删除切图源文件
        deleteAll(new File(cutSrcBasePath));
    }

    public static void srcPictureCut(File srcImageFile, String toFilePath, int widthStart, int heightStart, int widthEnd, int heightEnd) {
        try {
            // 读取源图像
            ImageIO.setUseCache(false);
            BufferedImage bi = ImageIO.read(srcImageFile);
            // 源图宽度
            int srcWidth = bi.getWidth();
            // 源图高度
            int srcHeight = bi.getHeight();
            // 切图宽度
            int cutWidth = widthEnd - widthStart;
            // 切图高度
            int cutHeight = heightEnd - heightStart;
            // 源文件
            /*
             * java提供了4个缩放的微调选项
             * image.SCALE_SMOOTH //平滑优先(图片质量好、切割速度慢)
             * image.SCALE_FAST //速度优先(图片质量差、切割速度快)
             * image.SCALE_AREA_AVERAGING //区域均值(图片质量好、切割速度慢)
             * image.SCALE_REPLICATE //像素复制型缩放(图片质量差、切割速度快)
             * image.SCALE_DEFAULT //默认缩放模式(图片质量差、切割速度快)
             */
            Image sourceImage = bi.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT);
            if(!isTransparent(bi,widthStart,heightStart,widthEnd,heightEnd)){
                // 绘制切图
                // 四个参数分别为图像起点坐标和宽高
                ImageFilter cropFilter = new CropImageFilter(widthStart, heightStart, widthEnd, heightEnd);
                Image cutImage = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(sourceImage.getSource(), cropFilter));
                BufferedImage cutBufferedImage = new BufferedImage(cutWidth, cutHeight, BufferedImage.TYPE_INT_RGB);
                Graphics2D cutG2d = cutBufferedImage.createGraphics();
                // 设置背景全透明
                // 返回 BufferedImage支持指定透明度,与此相适应的数据布局和颜色模型 GraphicsConfiguration。
                cutBufferedImage = cutG2d.getDeviceConfiguration().createCompatibleImage(cutWidth, cutHeight, Transparency.TRANSLUCENT);
                cutG2d = cutBufferedImage.createGraphics();
                // 消除锯齿
                cutG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                cutG2d.drawImage(cutImage, 0, 0, null);
                cutG2d.dispose();
                // 输出为文件
                File file = new File(toFilePath);
                if(!file.getParentFile().exists()){
                    file.getParentFile().mkdirs();
                }
                ImageIO.write(cutBufferedImage, "PNG", file);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 适配地图的瓦片切图工具
     * @param srcImageFile  源图片
     * @param toFilePath    切片目标文件分组
     * @param destWidth     目标切片宽度px
     * @param destHeight    目标切片高度px
     * @param level         图片所在层级
     * @param cutStartLevel 切图开始层级
     * @param cutEndLevel   切图结束层级
     * @param tileNumberX   x轴初始瓦片参数值
     * @param tileNumberY   y轴初始瓦片参数值
     */
    public static void pictureCut(File srcImageFile, String toFilePath, int destWidth, int destHeight, int level,
                                  int cutStartLevel, int cutEndLevel, int tileNumberX, int tileNumberY) {
        try {
            // 读取源图像
            ImageIO.setUseCache(false);
            BufferedImage bi = ImageIO.read(srcImageFile);
            // 源图宽度
            int srcWidth = bi.getWidth();
            // 源图高度
            int srcHeight = bi.getHeight();
            // 切图宽度
            int cutWidth = bi.getWidth();
            // 切图高度
            int cutHeight = bi.getHeight();
            // 控制切图宽高系数,减小切图误差
            int zz = 1;
            // z坐标控制
            for (int z = level; z <= cutEndLevel; z++) {
                // 从【切图开始层级】开始切图
                if (z >= cutStartLevel && srcWidth >= cutWidth && srcHeight >= cutHeight) {
                    // 源文件
                    /*
                     * java提供了4个缩放的微调选项
                     * image.SCALE_SMOOTH //平滑优先(图片质量好、切割速度慢)
                     * image.SCALE_FAST //速度优先(图片质量差、切割速度快)
                     * image.SCALE_AREA_AVERAGING //区域均值(图片质量好、切割速度慢)
                     * image.SCALE_REPLICATE //像素复制型缩放(图片质量差、切割速度快)
                     * image.SCALE_DEFAULT //默认缩放模式(图片质量差、切割速度快)
                     */
                    Image sourceImage = bi.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT);
                    // 切片横向数量
                    int cols = 0;
                    // 切片纵向数量
                    int rows = 0;
                    // 计算切片的横向和纵向数量
                    if (srcWidth % cutWidth == 0) {
                        cols = srcWidth / cutWidth;
                    } else {
                        cols = (int) Math.floor(srcWidth / cutWidth) + 1;
                    }
                    if (srcHeight % cutHeight == 0) {
                        rows = srcHeight / cutHeight;
                    } else {
                        rows = (int) Math.floor(srcHeight / cutHeight) + 1;
                    }
                    // 循环建立切片(横向切片)
                    for (int i = 0; i < rows; i++) {
                        for (int j = 0; j < cols; j++) {
                            int widthStart = j * cutWidth;
                            int heightStart = i * cutHeight;
                            int widthEnd = (j + 1) * cutWidth;
                            if(widthEnd > srcWidth){
                                widthEnd = srcWidth;
                            }
                            int heightEnd = (i + 1) * cutHeight;
                            if(heightEnd > srcHeight){
                                heightEnd = srcHeight;
                            }
                            if(!isTransparent(bi,widthStart,heightStart,widthEnd,heightEnd)){
                                // 绘制切图
                                // 四个参数分别为图像起点坐标和宽高
                                ImageFilter cropFilter = new CropImageFilter(widthStart, heightStart, widthEnd, heightEnd);
                                Image cutImage = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(sourceImage.getSource(), cropFilter));
                                BufferedImage bufferedImage = new BufferedImage(cutWidth, cutHeight, BufferedImage.TYPE_INT_RGB);
                                Graphics2D cutG2d = bufferedImage.createGraphics();
                                // 设置背景全透明
                                // 返回 BufferedImage支持指定透明度,与此相适应的数据布局和颜色模型 GraphicsConfiguration。
                                bufferedImage = cutG2d.getDeviceConfiguration().createCompatibleImage(cutWidth, cutHeight, Transparency.TRANSLUCENT);
                                cutG2d = bufferedImage.createGraphics();
                                // 消除锯齿
                                cutG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                                cutG2d.drawImage(cutImage, 0, 0, null);
                                cutG2d.dispose();
                                // 通过切图绘制缩小后的瓦片图
                                if(destWidth != 0 && destHeight != 0){
                                    /*
                                     * java提供了4个缩放的微调选项
                                     * image.SCALE_SMOOTH //平滑优先(图片质量好、切割速度慢)
                                     * image.SCALE_FAST //速度优先(图片质量差、切割速度快)
                                     * image.SCALE_AREA_AVERAGING //区域均值(图片质量好、切割速度慢)
                                     * image.SCALE_REPLICATE //像素复制型缩放(图片质量差、切割速度快)
                                     * image.SCALE_DEFAULT //默认缩放模式(图片质量差、切割速度快)
                                     */
                                    Image destImage = bufferedImage.getScaledInstance(destWidth, destHeight, Image.SCALE_SMOOTH);
                                    bufferedImage = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB);
                                    Graphics2D destG2d = bufferedImage.createGraphics();
                                    // 设置背景全透明
                                    // 返回 BufferedImage支持指定透明度,与此相适应的数据布局和颜色模型 GraphicsConfiguration。
                                    bufferedImage = destG2d.getDeviceConfiguration().createCompatibleImage(destWidth, destHeight, Transparency.TRANSLUCENT);
                                    destG2d = bufferedImage.createGraphics();
                                    // 消除锯齿
                                    destG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                                    destG2d.drawImage(destImage, 0, 0, null);
                                    destG2d.dispose();
                                }
                                // 输出为文件
                                File file = new File(toFilePath + "/" + z + "/" + z + "-" + (j + tileNumberX) + "-" + (i + tileNumberY) + ".png");
                                if(!file.getParentFile().exists()){
                                    file.getParentFile().mkdirs();
                                }
                                ImageIO.write(bufferedImage, "PNG", file);
                            }
                        }
                    }
                }
                // 瓦片参数值重置
                tileNumberY *= 2;
                tileNumberX *= 2;
                // 切片参宽高度重置
                cutWidth = (int) Math.ceil(1.0*srcWidth / (1 << zz));
                cutHeight = (int) Math.ceil(1.0*srcHeight / (1 << zz));
                zz++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 裁剪、拉伸、压缩PNG图片工具类
     * @param fromFile     源文件
     * @param toFile       裁剪后的文件
     * @param outputWidth  裁剪宽度px(小于等于0时使用原图尺寸)
     * @param outputHeight 裁剪高度px(小于等于0时使用原图尺寸)
     * @param offsetTop    上轴偏移量(左上角为基准)
     * @param offsetRight  右轴偏移量(左上角为基准)
     * @param offsetBottom 下轴偏移量(左上角为基准)
     * @param offsetLeft   左轴偏移量(左上角为基准)
     * @param proportion   是否是等比缩放
     */
    public static void resizePng(File fromFile, File toFile, int outputWidth, int outputHeight, int offsetTop, int offsetRight, int offsetBottom, int offsetLeft, boolean proportion) {
        try {
            ImageIO.setUseCache(false);
            BufferedImage bi = ImageIO.read(fromFile);
            if (outputWidth <= 0) {
                outputWidth = bi.getWidth();
            }
            if (outputHeight <= 0) {
                outputHeight = bi.getHeight();
            }
            int newWidth;
            int newHeight;
            // 判断是否是等比缩放
            if (proportion) {
                // 为等比缩放计算输出的图片宽度及高度
                double rate1 = ((double) bi.getWidth(null)) / (double) outputWidth + 0.1;
                double rate2 = ((double) bi.getHeight(null)) / (double) outputHeight + 0.1;
                // 根据缩放比率大的进行缩放控制
                double rate = rate1 < rate2 ? rate1 : rate2;
                newWidth = (int) (((double) bi.getWidth(null)) / rate);
                newHeight = (int) (((double) bi.getHeight(null)) / rate);
            } else {
                // 输出的图片宽度
                newWidth = outputWidth;
                // 输出的图片高度
                newHeight = outputHeight;
            }
            /*
             * java提供了4个缩放的微调选项
             * image.SCALE_SMOOTH //平滑优先(图片质量好、切割速度慢)
             * image.SCALE_FAST //速度优先(图片质量差、切割速度快)
             * image.SCALE_AREA_AVERAGING //区域均值(图片质量好、切割速度慢)
             * image.SCALE_REPLICATE //像素复制型缩放(图片质量差、切割速度快)
             * image.SCALE_DEFAULT //默认缩放模式(图片质量差、切割速度快)
             */
            Image image = bi.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH);
            BufferedImage bufferedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = bufferedImage.createGraphics();
            // 设置背景全透明
            // 返回 BufferedImage支持指定透明度,与此相适应的数据布局和颜色模型 GraphicsConfiguration。
            System.err.println("【切图】新合成图大小:"+(newWidth + offsetLeft + offsetRight)+"x"+(newHeight + offsetTop + offsetBottom));
            bufferedImage = g2d.getDeviceConfiguration().createCompatibleImage(newWidth + offsetLeft + offsetRight, newHeight + offsetTop + offsetBottom, Transparency.TRANSLUCENT);
            g2d.dispose();
            g2d = bufferedImage.createGraphics();
            // 消除锯齿
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.drawImage(image, offsetLeft, offsetTop, null);
            g2d.dispose();
            if(!toFile.getParentFile().exists()){
                toFile.getParentFile().mkdirs();
            }
            ImageIO.write(bufferedImage, "PNG", toFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据瓦片参数和偏移量合成切割源图
     * @param fromFile  源文件
     * @param toFile    目标文件
     * @param offsetTop 上偏移量
     * @param offsetRight   右偏移量
     * @param offsetBottom  下偏移量
     * @param offsetLeft    左偏移量
     * @param cellPixel 瓦片图像素
     * @param tileLevel 源图理论层级
     */
    public static Map<String,Object> composeCutSrcPng(File fromFile, File toFile, double offsetTop, double offsetRight, double offsetBottom, double offsetLeft, int cellPixel, int tileLevel) {
        Map<String, Object> map = new HashMap<String, Object>();
        // 图片占有像素
        double posseWidth = cellPixel-offsetLeft-offsetRight;
        double posseHeight = cellPixel-offsetTop-offsetBottom;
        try {
            // 计算原图实际偏移尺寸
            ImageIO.setUseCache(false);
            BufferedImage bufferedImage = ImageIO.read(fromFile);
            // 源图尺寸
            int width = bufferedImage.getWidth();
            int height = bufferedImage.getHeight();
            // 拉伸后图片的尺寸
            int tensileWidth = width;
            int tensileHeight = height;
            if(width > height){
                tensileHeight = (int) Math.round(width*posseHeight/posseWidth);
                // 缩放
                if(tensileHeight < height){
                    tensileHeight = height;
                    tensileWidth = (int) Math.round(height*(posseWidth)/(posseHeight));
                }
            }else{
                tensileWidth = (int) Math.round(height*(posseWidth)/(posseHeight));
                // 缩放
                if(tensileWidth < width){
                    tensileWidth = width;
                    tensileHeight = (int) Math.round(width*posseHeight/posseWidth);
                }
            }
            // 计算拉伸后图片的实际偏移量
            double realTop = offsetTop*tensileHeight/posseHeight;
            double realRight = offsetRight*tensileWidth/posseWidth;
            double realBottom = offsetBottom*tensileHeight/posseHeight;
            double realLeft = offsetLeft*tensileWidth/posseWidth;
            // 自动生成合理尺寸的原图
            int widthTotal = (int) Math.round(tensileWidth + realRight + realLeft);
            int heightTotal = (int) Math.round(tensileHeight + realTop + realBottom);
            // 自动优化图片为正方形,且生成对应tileLevel层级的标准尺寸图片,减小切图误差
            // 切图的标准尺寸
            int standardSize = 0;
            // 对应层级横向(或纵向)切割图片数
            int zoom = 1 << (18-tileLevel);
            // 计算标准尺寸切割图实际偏移尺寸
            if(widthTotal >= heightTotal){
                standardSize = (int) (Math.ceil(1.0*widthTotal/zoom)*zoom);
            }else if(widthTotal < heightTotal){
                standardSize = (int) (Math.ceil(1.0*heightTotal/zoom)*zoom);
            }
            int widthPadding = (standardSize - widthTotal);
            int heightPadding = (standardSize - heightTotal);
            if(widthPadding != 0){
                int rightNum = (int) (widthPadding*realRight/widthTotal);
                int leftNum = (int) (widthPadding*realLeft/widthTotal);
                realRight += rightNum;
                realLeft += leftNum;
                tensileWidth += widthPadding - rightNum - leftNum;
            }
            if(heightPadding != 0){
                int topNum = (int) (heightPadding*realTop/heightTotal);
                int bottomNum = (int) (heightPadding*realBottom/heightTotal);
                realTop += topNum;
                realBottom += bottomNum;
                tensileHeight += heightPadding - topNum - bottomNum;
            }
            // 生成拉伸后的图片
            resizePng(fromFile,toFile,tensileWidth,tensileHeight,0,0,0,0,false);
            // 返回偏移参数信息
            map.put("standardSize",standardSize);
            map.put("tensileWidth",tensileWidth);
            map.put("tensileHeight",tensileHeight);
            map.put("realTop",(int)Math.round(realTop));
            map.put("realRight",(int)Math.round(realRight));
            map.put("realBottom",(int)Math.round(realBottom));
            map.put("realLeft",(int)Math.round(realLeft));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

    /**
     * 谷歌下转换经纬度对应的层行列
     *
     * @param lon  经度
     * @param lat  维度
     * @param zoom 在第zoom层进行转换
     * @return
     */
    public static int[] LonLatToXYZ(double lon, double lat, int zoom) {
        double n = Math.pow(2, zoom);
        double tileX = ((lon + 180) / 360) * n;
        double tileY = (1 - (Math.log(Math.tan(Math.toRadians(lat)) + (1 / Math.cos(Math.toRadians(lat)))) / Math.PI)) / 2 * n;
        int[] xy = new int[2];
        xy[0] = (int) Math.floor(tileX);
        xy[1] = (int) Math.floor(tileY);
        return xy;
    }

    /**
     * 瓦片参数转经纬度
     *
     * @param x 经度瓦片参数
     * @param y 纬度瓦片参数
     * @param z z轴层级
     * @return
     */
    public static double[] XYZtoLonlat(int x, int y, int z) {
        double n = Math.pow(2, z);
        double lon = x / n * 360.0 - 180.0;
        double lat = Math.atan(Math.sinh(Math.PI * (1 - 2 * y / n)));
        lat = lat * 180.0 / Math.PI;
        double[] lonlat = new double[2];
        lonlat[0] = lon;
        lonlat[1] = lat;
        return lonlat;
    }

    /**
     * 经度到像素X值
     * @param lon   经度
     * @param level z轴层级
     * @return
     */
    static double lonToPixel(double lon, int level) {
        return (lon + 180) * (256 << level) / 360;

    }

    /**
     * 像素X到经度
     * @param pixelX    像素X
     * @param level z轴层级
     * @return
     */
    static double pixelToLon(double pixelX, int level) {
        return pixelX * 360 / (256 << level) - 180;

    }

    /**
     * 纬度到像素Y
     * @param lat   纬度
     * @param level z轴层级
     * @return
     */
    static double latToPixel(double lat, int level) {
        double siny = Math.sin(lat * Math.PI / 180);
        double y = Math.log((1 + siny) / (1 - siny));
        return (128 << level) * (1 - y / (2 * Math.PI));
    }

    /**
     * 像素Y到纬度
     * @param pixelY    像素Y
     * @param level z轴层级
     * @return
     */
    static double pixelToLat(double pixelY, int level) {
        // 当然还可以更精确
        final double E = 2.7128;
        double y = 2 * Math.PI * (1 - pixelY / (128 << level));
        double z = power(E, y);
        double siny = (z - 1) / (z + 1);
        return Math.asin(siny) * 180 / Math.PI;
    }

    /**
     * 根据层级确定图片上右下左的偏移量
     * @param lonStar       开始经度
     * @param latStar       开始纬度
     * @param lonEnd        结束经度
     * @param latEnd        结束纬度
     * @param cellPixel     瓦片单元格大小px(默认256)
     * @return
     */
    public static Map<String, Object> getOffset(double lonStar, double latStar, double lonEnd, double latEnd, int cellPixel) {
        int[] star = {};
        int[] end = {};
        // 切图理论层级
        int level = 18;
        do {
            star = LonLatToXYZ(lonStar, latStar, level);
            end = LonLatToXYZ(lonEnd, latEnd, level);
            level--;
        } while (star[0] != end[0] || star[1] != end[1]);
        Map<String, Object> map = new HashMap<String, Object>();
        level += 1;
        int[] tileNumber = LonLatToXYZ(lonStar, latStar, level);
        int tileNumberX = tileNumber[0];
        int tileNumberY = tileNumber[1];
        map.put("level", level);
        map.put("top", latToPixel(latStar, level) - (cellPixel * tileNumberY));
        map.put("right", cellPixel * (tileNumberX + 1) - lonToPixel(lonEnd, level));
        map.put("bottom", cellPixel * (tileNumberY + 1) - latToPixel(latEnd, level));
        map.put("left", lonToPixel(lonStar, level) - (cellPixel * tileNumberX));
        map.put("tileNumberX", tileNumber[0]);
        map.put("tileNumberY", tileNumber[1]);
        return map;
    }

    /**
     * 判断图片是否为纯透明背景图
     * @param bufImg        图片流
     * @param widthStart    判断宽的起点
     * @param heightStart   判断高的起点
     * @param widthEnd      判断宽的终点
     * @param heightEnd     判断高的终点
     * @return
     */
    public static boolean isTransparent(BufferedImage bufImg, int widthStart, int heightStart, int widthEnd, int heightEnd){
        widthStart = widthStart<=0?0:widthStart;
        heightStart = heightStart<=0?0:heightStart;
        widthEnd = widthEnd<=0?bufImg.getWidth():widthEnd;
        heightEnd = heightEnd<=0?bufImg.getHeight():heightEnd;
        for (int i = widthStart; i < widthEnd; i++) {
            for (int j = heightStart; j < heightEnd; j++) {
                int pixel = bufImg.getRGB(i, j);
                if (pixel >> 24 != 0) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 获取当前文件夹下的所有文件
     * @param path
     * @param allfilelist
     * @return
     */
    public static ArrayList<File> getallfile(File path, ArrayList<File> allfilelist) {
        // 路径存在
        if (path.exists()) {
            // 判断文件是否是文件夹,如果是,开始递归
            if (path.isDirectory()) {
                File f[] = path.listFiles();
                for (File file2 : f) {
                    getallfile(file2, allfilelist);
                }
            } else {
                allfilelist.add(path);
            }
        }
        return allfilelist;
    }

    /**
     * 删除所有文件
     * @param path
     */
    public static void deleteAll(File path) {
        // 路径存在
        if (!path.exists()){
            return;
        }
        // 是文件
        if (path.isFile()){
            path.delete();
            return;
        }
        File[] files = path.listFiles();
        for (int i = 0; i < files.length; i++) {
            deleteAll(files[i]);
        }
        path.delete();
    }

    public static void main(String[] args) throws IOException {
        String group = "djy-test";
        String tempFilePath = "C:/myFile/pictureCut";
//        String toFilePath = tempFilePath + "/" +  group+ "/djy-big.png";
//        String drawingMapImg = "https://htwz-wzy.obs.cn-north-4.myhuaweicloud.com:443/htwz-wzy/4199caf6-7d76-442f-b89d-60fc38d36fb3%E9%83%BD%E6%B1%9F%E5%A0%B0-huqingqing.png";
//        URL url = new URL(drawingMapImg);
//        FileUtils.copyURLToFile(url, new File(toFilePath));
//        File fromFile = new File(toFilePath);
        File fromFile = new File(tempFilePath + "/djy-src.png");

        // 切图开始层级
        int cutStartLevel = 11;
        int cutEndLevel = 18;
        // 首坐标
        double lon1 = 103.604822;
        double lat1 = 31.00991;
        // 尾坐标
        double lon2 = 103.618107;
        double lat2 = 30.993703;

//        // 拉伸坐标
//        // 首坐标
//        double lon1 = 103.60408;
//        double lat1 = 31.012203;
//        // 尾坐标
//        double lon2 = 103.62408;
//        double lat2 = 30.992203;

        Date currDate = new Date();
        tencentPictureCut(fromFile,tempFilePath, group, lon1, lat1, lon2, lat2, cutStartLevel, cutEndLevel);
        System.out.println("总时间:" + ((new Date()).getTime() - currDate.getTime()) / 1000);
        // 源文件删除
//        fromFile.delete();
    }
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、需求
  • 2、瓦片地图简介
  • 3、腾讯叠加自定义瓦片api
  • 4、切瓦片图思路
  • 5、遇到的问题及优化过程
    • 1、怎么根据首尾坐标对源图进行拉伸
      • 2、根据切割源图切图后和原图位置有偏移
        • 3、切图速度过慢
          • 4、服务器切图内存溢出
            • 4.1、手绘源图尺寸太大,比如超过50m时会存在服务器内存不够
            • 4.1、通过源图生成的切割源图尺寸过大
          • 5、瓦片图存在锯齿
            • 6、切图层级小于图片所在的理论层级时,合成的切割源图较大导致内存溢出
            • 6、参考文档连接
            • 7、PictureCutUtil工具封装
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档