0


大图切片预览

文章目录

前言

最近有需求,前端要预览百兆以上的大图,这直接访问应该就不太行了,系统打开都在加载好一会儿,刚好从事的又是 gis 行业,于是打算用类似加载地图的方式来切片加载大图。这里最好是按标准的切片方式来,这样就可以用现成的地图引擎来预览了。这里就按 TMS 标准来切片。


引用一下 ChatGPT 的回答

“TMS” 代表的是 “Tile Map Service”,是一种用于在Web地图应用中加载和显示地图瓦片的标准协议。瓦片地图是将地图划分成小块瓦片,每个瓦片包含地图的一部分信息,通过加载这些瓦片可以实现整个地图的显示。
.
TMS 瓦片标准是一种用于组织和管理这些地图瓦片的约定。以下是 TMS 瓦片标准的一些关键概念:
瓦片坐标系统: TMS 使用一个瓦片坐标系统,其中地图被划分为网格状的瓦片,每个瓦片由一个唯一的坐标标识。通常,左上角的瓦片坐标是 (0,0),并且随着地图的缩放级别的增加,瓦片的坐标也相应地增加。
缩放级别: TMS 支持不同的缩放级别,每个级别对应于地图的不同分辨率。每个缩放级别的瓦片数目是前一个级别的两倍。缩放级别通过整数值表示,例如,缩放级别为 0 表示最低级别,而缩放级别为 1 表示比级别 0 更高的分辨率。
瓦片命名规则: TMS 使用一种规范的瓦片命名规则,其中瓦片的坐标和缩放级别被编码到URL中。例如,一个瓦片的URL可能类似于 http://example.com/{z}/{x}/{y}.png,其中 {z} 表示缩放级别, {x} 和 {y} 表示瓦片的坐标。
坐标原点: TMS 有两种坐标原点的定义方式,一种是以地图左上角为原点,另一种是以地图左下角为原点。这两种方式在不同的实现中有不同的选择,但都在相应的文档中明确定义。

总体而言,TMS 瓦片标准通过定义一种通用的方式来命名和组织地图瓦片,使得不同的地图服务和应用程序可以遵循相同的规范,从而实现更好的互操作性。这种标准化有助于开发者创建和集成地图服务,同时也简化了地图数据的发布和共享。


TMS 的切片可以采用金字塔切片方式,缩放级别为 0 时表示最低级别,只有一个瓦片,随着缩放级别的增加,地图被划分成更多的瓦片,每个瓦片下一级可以拆成四个,所以每一层级瓦片数就是上一层级数的四倍。
单个瓦片尺寸通常是 256x256像素

这种感觉:
https://zhuanlan.zhihu.com/p/64736752?utm_id=0
图像来源:GIS理论知识(四)之地图的图层(切片/瓦片)概念


我们项目设计是前端是固定的几个大图预览,所以直接开发个工具来切片使用就可以了。

这里决定就用 Java 来开发,也是为了后续可能做后台管理打铺垫, 但 Java 这块图像操作相关 API 真不熟,直接上 ChatGPT 问一下。

开始用 BufferedImage 来实现,但是效率不是太高,网上查了 OpenCV 效率貌似很高,直接让 ChatGPT用 OpenCV 再实现一遍,实践对比了下确实提升很大

分享一波 ChatGPT 问答
在这里插入图片描述

基于这代码改一点点,就可以完美实现了!!

处理流程

  1. 判断图像分辨率是否是 256x256 的整数倍,如果不是则需要扩大补图。(如果不这样做切好的瓦片肯定会有分辨率小于256 x 256 的,部分地图引擎可能会直接拉伸尺寸导致变形)Mat inputImage =Imgcodecs.imread("xxx.tif");// 标准切片是正方形,只需要判断宽高最大值是否是 256 的整数倍即可int max =Math.max(inputImage.cols(), inputImage.rows());if(max % tileSize !=0){double ceil =Math.ceil(max /(double) tileSize); inputImage =mergeTile(inputImage,(int) ceil * tileSize);}
  2. 对处理好的图像开始切片。int useLevel = 当前层级;for(int y =0; y < inputImage.rows(); y += tileSize){for(int x =0; x < inputImage.cols(); x += tileSize){// 第三四参数直接 tileSize 也可以,开始这么写是因为没有对图像尺寸做补图处理,防止超出图像尺寸报错。Rect roi =newRect(x, y,Math.min(tileSize, inputImage.cols()- x),Math.min(tileSize, inputImage.rows()- y));Mat tile =newMat(inputImage, roi);// 输出文件,如果做网络服务的话做好索引存数据库我感觉更好。File outputTileFile =newFile(outputPath, useLevel +File.separator + x / tileSize +File.separator + y / tileSize +".jpg");if(!outputTileFile.getParentFile().exists()){ outputTileFile.getParentFile().mkdirs();}Imgcodecs.imwrite(outputTileFile.getAbsolutePath(), tile);}}
  3. 切完一级将图像尺寸缩放一半,如果缩放一半后尺寸仍 >= 256x256,就继续循环切片。反之就结束。do{// 切片// ...Imgproc.resize(inputImage, inputImage,newSize(inputImage.cols()/2, inputImage.rows()/2));}while((inputImage.cols()>= tileSize && inputImage.rows()>= tileSize))

这里打好了一个 jar 包,欢迎大家使用体验! 下载地址

在这里插入图片描述

完整代码

packagetop.easydu.easytools.utils;importorg.opencv.core.*;importorg.opencv.imgcodecs.Imgcodecs;importorg.opencv.imgproc.Imgproc;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.io.File;importjava.io.FileNotFoundException;importjava.io.IOException;importjava.net.URL;importjava.util.ArrayList;importjava.util.List;importjava.util.HashMap;importjava.util.Map;publicclassImageUtils{static{// 加载动态库,这个就是加载的 resources 目录的dllLibUtil.loadResourcesLibrary("lib/opencv/x64/opencv_java455.dll");}privatestaticfinalLogger log =LoggerFactory.getLogger(ImageUtils.class);publicstaticclassImageSplitResult{/**
         * 瓦片数量
         */publicint tileCount =0;/**
         * 层级数
         */publicint levels =0;@OverridepublicStringtoString(){return"ImageSplitResult{"+"tileCount="+ tileCount +", levels="+ levels +'}';}}/**
     * 默认瓦片大小
     */privatestaticfinalintDEFAULT_TILE_SIZE=256;/**
     * 计算有多少级
     * @param width
     * @param height
     * @param tileSize
     * @return
     */privatestaticintcomputeLevel(int width,int height,int tileSize){int level =0;do{

            width = width /2;
            height = height /2;
            level++;}while(width >= tileSize && height >= tileSize);return level;}/**
     * 图片拆分
     * @param file 图像文件
     * @param outputPath 输出路径
     */publicstaticImageSplitResultsplitImage(File file,String outputPath)throwsIOException{if(!file.exists()){thrownewFileNotFoundException(file.getPath());}finalint tileSize =DEFAULT_TILE_SIZE;ImageSplitResult result =newImageSplitResult();Mat inputImage  =Imgcodecs.imread(file.getAbsolutePath());

        log.info(String.format("load image: %s x %s", inputImage.rows(), inputImage.cols()));// 分辨率补充int max =Math.max(inputImage.cols(), inputImage.rows());if(max % tileSize !=0){double ceil =Math.ceil(max /(double) tileSize);
            inputImage =mergeTile(inputImage,(int) ceil * tileSize);}File outDir =newFile(outputPath);if(!outDir.exists()){
            outDir.mkdirs();}long startTime =System.currentTimeMillis();int totalLevel =computeLevel(inputImage.cols(), inputImage.width(), tileSize);

        result.levels = totalLevel;int count =0;// 处理了几级do{long _start =System.currentTimeMillis();int useLevel = totalLevel - count -1;// Break the image into small tilesfor(int y =0; y < inputImage.rows(); y += tileSize){for(int x =0; x < inputImage.cols(); x += tileSize){Rect roi =newRect(x, y,Math.min(tileSize, inputImage.cols()- x),Math.min(tileSize, inputImage.rows()- y));Mat tile =newMat(inputImage, roi);// Save the tile to the output folderFile outputTileFile =newFile(outputPath,  useLevel +File.separator + x / tileSize +File.separator + y / tileSize +".jpg");if(!outputTileFile.getParentFile().exists()){
                        outputTileFile.getParentFile().mkdirs();}Imgcodecs.imwrite(outputTileFile.getAbsolutePath(), tile);
                    result.tileCount++;}}
            log.info(String.format("level: %s time: %s ms", useLevel,System.currentTimeMillis()- _start));Imgproc.resize(inputImage, inputImage,newSize(inputImage.cols()/2, inputImage.rows()/2));

            count ++;}while((inputImage.cols()>= tileSize && inputImage.rows()>= tileSize));

        log.info(String.format("切片完成, 耗时: %s MS",System.currentTimeMillis()- startTime));return result;}privatestaticMatmergeTile(Mat tile,int size){if(tile.rows()== size && tile.cols()== size){return tile;}Mat baseTile =newMat(size, size,CvType.CV_8UC3,Scalar.all(255));Rect newRoi =newRect(0,0, tile.cols(), tile.rows());Mat roiMat =newMat(baseTile, newRoi);
        tile.copyTo(roiMat);return baseTile;}publicstaticImageSplitResultsplitImage(String filePath,String outputPath)throwsIOException{returnsplitImage(newFile(filePath), outputPath);}}

前端预览

直接使用 leatlet 来加载切好的瓦片,效果还是很不错的 !!! 理论上支持 TMS 瓦片标准的地图引擎都可以直接使用的!
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


有问题或优化建议欢迎指导 ~~~


本文转载自: https://blog.csdn.net/qq_25211081/article/details/135355650
版权归原作者 du青松 所有, 如有侵权,请联系我们删除。

“大图切片预览”的评论:

还没有评论