大图切片预览

news2024/11/24 12:10:28

文章目录

  • 前言
  • 处理流程
  • 完整代码
  • 前端预览

前言

最近有需求,前端要预览百兆以上的大图,这直接访问应该就不太行了,系统打开都在加载好一会儿,刚好从事的又是 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 = new Rect(x, y, Math.min(tileSize, inputImage.cols() - x), Math.min(tileSize, inputImage.rows() - y));
              Mat tile = new Mat(inputImage, roi);
              // 输出文件,如果做网络服务的话做好索引存数据库我感觉更好。
              File outputTileFile = new File(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, new Size(inputImage.cols() / 2, inputImage.rows() / 2));
    } while ((inputImage.cols() >= tileSize && inputImage.rows() >= tileSize))
    

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

在这里插入图片描述

完整代码

package top.easydu.easytools.utils;

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;

public class ImageUtils {

    static {
        // 加载动态库,这个就是加载的 resources 目录的dll
        LibUtil.loadResourcesLibrary("lib/opencv/x64/opencv_java455.dll");
    }

    private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);

    public static class ImageSplitResult {

        /**
         * 瓦片数量
         */
        public int tileCount = 0;

        /**
         * 层级数
         */
        public int levels = 0;

        @Override
        public String toString() {
            return "ImageSplitResult{" +
                    "tileCount=" + tileCount +
                    ", levels=" + levels +
                    '}';
        }
    }


    /**
     * 默认瓦片大小
     */
    private static final int DEFAULT_TILE_SIZE = 256;

    /**
     * 计算有多少级
     * @param width
     * @param height
     * @param tileSize
     * @return
     */
    private static int computeLevel(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 输出路径
     */
    public static ImageSplitResult splitImage(File file, String outputPath) throws IOException {

        if (!file.exists()) {
            throw new FileNotFoundException(file.getPath());
        }

        final int tileSize = DEFAULT_TILE_SIZE;

        ImageSplitResult result = new ImageSplitResult();

        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 = new File(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 tiles
            for (int y = 0; y < inputImage.rows(); y += tileSize) {
                for (int x = 0; x < inputImage.cols(); x += tileSize) {
                    Rect roi = new Rect(x, y, Math.min(tileSize, inputImage.cols() - x), Math.min(tileSize, inputImage.rows() - y));
                    Mat tile = new Mat(inputImage, roi);
                    // Save the tile to the output folder
                    File outputTileFile = new File(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, new Size(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;
    }

    private static Mat mergeTile(Mat tile, int size) {

        if (tile.rows() == size && tile.cols() == size) {
            return tile;
        }
        Mat baseTile = new Mat(size, size, CvType.CV_8UC3, Scalar.all(255));
        Rect newRoi = new Rect(0, 0, tile.cols(), tile.rows());
        Mat roiMat = new Mat(baseTile, newRoi);
        tile.copyTo(roiMat);

        return baseTile;
    }

    public static ImageSplitResult splitImage(String filePath, String outputPath) throws IOException {

        return splitImage(new File(filePath), outputPath);

    }
}

前端预览

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

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


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1369866.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【HarmonyOS】掌握 Stage 模型的核心概念与应用

从今天开始&#xff0c;博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”&#xff0c;对于刚接触这项技术的小伙伴在学习鸿蒙开发之前&#xff0c;有必要先了解一下鸿蒙&#xff0c;从你的角度来讲&#xff0c;你认为什么是鸿蒙呢&#xff1f;它出现的意义又是…

谷歌提出「边界注意力」模型,实现超越像素级检测精度!微弱边界也逃不过

有些情况下&#xff0c;当面临分辨率较低的图像时&#xff0c;可能会在进行诸如目标检测和图像分割等任务时遇到一些挑战和阻碍。这是因为低分辨率图像可能丢失了细节信息&#xff0c;使得计算机视觉系统难以准确捕捉和理解图像中的关键特征。在这种背景下&#xff0c;传统的方…

Poi实现根据word模板导出-图表篇

往期系列传送门&#xff1a; Poi实现根据word模板导出-文本段落篇 &#xff08;需要完整代码的直接看最后位置&#xff01;&#xff01;&#xff01;&#xff09; 前言&#xff1a; 补充Word中图表的知识&#xff1a; 每个图表在word中都有一个内置的Excel&#xff0c;用于…

kubernetes 容器监控 Sysdig Falco

开头语 写在前面&#xff1a;如有问题&#xff0c;以你为准&#xff0c; 目前24年应届生&#xff0c;各位大佬轻喷&#xff0c;部分资料与图片来自网络 内容较长&#xff0c;页面右上角目录方便跳转 Sysdig 监控容器系统调用 介绍 资料 Sysdig:一个非常强大的系统监控、分…

PostgreSQL的常见错误和解决方法

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 在学习新的东西时&#xff0c;会犯很多的错误&#xff0c;会遇到很多坑。我们在填坑与犯错中不断进步成长。 以下是在学习pgsql中…

【驱动序列】C#获取电脑硬件之CPU信息,以及它都有那些品牌

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是是《驱动序列》文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识…

【深度学习】SDXL tensorRT 推理,Stable Diffusion 转onnx,转TensorRT

文章目录 sdxl 转 diffusers转onnx转TensorRT sdxl 转 diffusers def convert_sdxl_to_diffusers(pretrained_ckpt_path, output_diffusers_path):import osos.environ["HF_ENDPOINT"] "https://hf-mirror.com" # 设置 HF 镜像源&#xff08;国内用户使…

k8s的集群调度:

k8s的集群调度&#xff1a; Scheduler:负责调度资源&#xff0c;把pod调度到node节点 预算策略 优先策略 list-watch k8s集群当中&#xff0c;通过list-watch的机制进行每个组件的协作&#xff0c;保持数据同步&#xff0c;每个组件之间的解耦 Kubectl配置文件&#xff0c…

1.9 day7 IO进程线程

使用消息队列完成两个进程间的通信 进程1 #include <myhead.h> struct migbuf {long a;//消息类型char b[1024];//消息正文 }; #define SIZE (sizeof(struct migbuf)-sizeof(long)) int main(int argc, const char *argv[]) {//创建key值key_t key0;if((keyftok(".…

从文本(.txt)文件中读取数据时出现中文乱码

前言 当需要从记事本中读取数据时&#xff0c;发现读取的数据会出现中文乱码&#xff0c;我尝试了C和C读取文件&#xff0c;发现都是这样。 乱码原因 文本文件的保存默认使用UTF-8编码方式&#xff0c;而VS编译器的编码方式是GBK&#xff0c;所以不同的编码方式导致了乱码。…

6.1.2捕捉图像(内含5D博客长截图,你们都去哪儿了?)

6.1.2捕捉图像 利用HyperSnap6可以很方便地捕捉全屏、虚拟桌面、窗口、控件、整页、按钮、活动窗口和区域。除此之外&#xff0c;它还可以进行自由捕捉和特殊捕捉。 1&#xff0e;捕捉窗口或控件 利用传统的“PrintScreen”或“AltPrintScreen”键只能捕捉整个屏幕或当前活动…

numpy100练习题,包含相应使用函数解释

取自github开源项目&#xff1a;numpy100题 文章目录 1. 导入numpy库并简写为 np (★☆☆)2. 打印numpy的版本和配置说明 (★☆☆)3. 创建一个长度为10的空向量 (★☆☆)4. 如何找到任何一个数组的内存大小&#xff1f; (★☆☆)5. 如何从命令行得到numpy中add函数的说明文档?…

【Python学习】Python学习10-列表

目录 【Python学习】Python学习10-列表 前言创建语法访问列表中的值更新和删除列表元素操作列表列表截取Python列表函数&方法参考 文章所属专区 Python学习 前言 本章节主要说明Python的列表List。 创建语法 创建一个列表 通过方括号和逗号分割创建&#xff0c;列表数据…

springboot学生成绩管理系统源码和论文

随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&#xf…

书生大模型全链路开源体系

书生浦语大模型全链路开源体系开源了哪些东西 数据书生万卷&#xff1a;一个2TB的涵盖多种模态与任务的数据集预训练InternLM-Train&#xff1a;微调XTuner&#xff1a;可供你低成本微调模型的工具箱部署LMDeploy&#xff1a;一个服务端场景下、transformer 结构 LLM 部署工具…

使用开源通义千问模型(Qwen)搭建自己的大模型服务

目标 1、使用开源的大模型服务搭建属于自己的模型服务&#xff1b; 2、调优自己的大模型&#xff1b; 选型 采用通义千问模型&#xff0c;https://github.com/QwenLM/Qwen 步骤 1、下载模型文件 开源模型库&#xff1a;https://www.modelscope.cn/models mkdir -p /data/…

Camunda ServiceTask

一&#xff1a;Java class Java class实现JavaDelegate接口&#xff0c;只需要配置类的全限定名即可&#xff0c;不需要被Spring容器管理。 public class JavaClassServiceTask implements JavaDelegate {Overridepublic void execute(DelegateExecution execution) throws …

k8s的集群调度---下

前情回顾 预算策略&#xff1a;过滤出合适的节点 优选策略&#xff1a;选择部署的节点 nodeName&#xff1a;硬匹配&#xff0c;不走调度策略。node01. nodeSelector&#xff1a;根据节点的标签选择&#xff0c;会走调度算法。 只要是走调度算法&#xff0c;在不满足预算策…

荣誉 | 数说故事荣登2023粤港澳大湾区科创百强;上榜甲子光年2023中国数字经济榜

硬科技向前&#xff0c;加「数」战新年 2024新年伊始&#xff0c;数说故事就迎来了两大喜讯 上榜2023粤港澳大湾区科创榜 荣登【光年20】2023中国数字经济产品创新榜 ...... 以技术为笔&#xff0c;创新为墨 「数」写着数说故事在科技领域的强大实力与潜力 为新年注入更…

Selenium 学习(0.18)——软件测试之基本路径测试

1、基本路径法测试的概念 是一种白盒测试方法&#xff0c;它在程序控制流图的基础上&#xff0c;通过分析控制构造的环行复杂性&#xff0c;导出基本可执行路径集合&#xff0c;从而设计测试用例的方法。 要保证在测试中程序的每一个可执行语句至少执行一次 【这和语句…