I have a dream too !
-
开源技术赋予我们站在巨人的肩膀上做到更高更强的可能,我想通过开源技术来构建一个地理信息的世界,愿给地理信息数据带来更多的使用价值。
-
既然都说人类活动所接触、产生的信息80%以上都与地理空间位置有关,那么这些空间数据就应该很容易的被使用,而不是仅被围困在专业领域内,不是吗?
-
-
如果你想要拥有从未有过的东西,那就去做你从未做过的事!
I have a dream too !
开源技术赋予我们站在巨人的肩膀上做到更高更强的可能,我想通过开源技术来构建一个地理信息的世界,愿给地理信息数据带来更多的使用价值。
既然都说人类活动所接触、产生的信息80%以上都与地理空间位置有关,那么这些空间数据就应该很容易的被使用,而不是仅被围困在专业领域内,不是吗?
如果你想要拥有从未有过的东西,那就去做你从未做过的事!
前言 瓦片缓存组件是绝大部分瓦片服务所应该有的模块之一,而Geo Atlas同样实现了其瓦片缓存模块。本文用于描述Geo Atlas中的Cache模块的设计与实现过程。 什么是GAC? GAC,全称是Geo Atlas Cache,是Geo Atlas类库中的Cache模块,用于提供矢量瓦片的缓存功能。GAC源自GWC(GeoWebCache),是在GWC的基础上进行了适应性的调整而来。目前支持基于内存、文件系统两种缓存方式,且此两种缓存方式可任意组合。对于瓦片缓存处理策略,目前支持Seed、Reseed、Truncate三种,与GWC保持一致。 GAC的基本理念 GWC中声明并强调TileLayer的概念,并基于此抽象,用以适配数据来源与瓦片存储(缓存)。也就如同GeoServer中,一个图层如果需要拥有缓存能力,那么还需要创建一个TileLayer;也就是说,一个拥有缓存的图层,将会同时持有两个Layer,一个是 Map Layer(FeatureType),另一个是TileLayer。 TileLayer中进行瓦片存储相关内容的配置,可以随意配置存储容器对象(文件系统、对象存储、数据库等)。 而目前Geo Atlas Cache的实现则将大大简化这一操作,没有TileLayer对象,全局共用同一个存储容器对象,无需繁琐的存储配置,通过自动装配快速启用缓存,我认为这是中小项目中所需要的。 目前对于瓦片缓存(Tile Cache)的清理,也就是同GWC中提供的Seed、Reseed、Truncate一般。Truncate只需持有BlobStore的句柄即可完成,但Seed和Reseed则需前往数据的源端获取瓦片,进而才可完成操作,也就是需要持有获取源端瓦片Generate的句柄才可。或许这就是为什么GWC中提出TileLayer的原因之一也未可知 😮。 不过,我却由此认为GWC的边界不清晰,我认为缓存就做缓存的事情就可以了,应当把Layer、TileMatrixSet(GridSet)和Cache分开。但如此,若想要支持Seed和Reseed这两种给策略的话,至少需要提供一个拓展点才可。我在此将其命名为TileSource,是为Cache与Source(源端瓦片)之间的适配组件。其实,这不也是一种等同TileLayer的存在,但我并不通过Layer来进行关联控制,也没有TileLayer的概念,缓存就是缓存。 GAC的设计与实现 因为GeoServer沉重的历史包袱以及大而全的臃肿,所以有了Geo Atlas项目。GAC也将延续此理念,去除Cache特定于Layer的概念,无需为图层单独配置缓存,因为绝大部分情况下,都是使用相同的缓存配置。同时,他应该是可以被快速集成,且易于配置的。为了提升缓存组件的易用性、兼容性及稳定性,至少应该提供两种不同的缓存存储对象,且其中一种应该是基于内存的,另外一种是支持持久化的。当数据量很少时,可以关闭缓存或仅开启内存缓存;当数据量较大时,可以仅开启持久化缓存或同时开启内存缓存。内存缓存与可持久化缓存可自由搭配,任意组合。当两者全开启时,可形成两级缓存,此时需注意两级缓存间数据的同步。 GAC的需求与设计 接下来,再次确定一下GAC的需求: 支持矢量瓦片缓存 提供两类缓存存储对象,其中一种应该是基于内存的,另外一种是支持持久化的,且两者可任意组合,同时开启可形成二级缓存结构 提供Seed、Reseed、Truncate此三种瓦片缓存清理策略 全局共用同一个缓存存储对象,提供快速集成能力 其中,二级缓存是此前没有接触过的内容。结合自我臆想,给出了如下设想 🫣: 此二级缓存,可自行确定组合方式,并非需要两者同时开启。难点:状态同步(数据一致性) 一级缓存(基于内存): Local Mem Cache + LRU Guava Caffeine 二级缓存(可持久化):Outer Cache Redis File System Database GeoPackage 每一个Level一个gpkg文件,提升并发读写能力(支持配置为所有Level用一个gpkg,但是不推荐) PostGIS 不过,在经过一番调研之后,还是决定抄GeoWebCache的作业 😧。一是确实有一定的难度,二是目前时间有限,GAP中的我早已瑟瑟发抖 🙄。最重要的是,GeoWebCache中的MemoryBlobStore已经实现了上述二级缓存的需求呀 🫡,如此操作可直接覆盖掉前三个需求。而此时二级缓存的实现确定为: 基于内存的缓存(Guava) 基于文件系统的缓存 需要特别注意的是,此MemoryBlobStore二级缓存是可拓展架构,后续可自行拓展不同的Provider。 💡 对于Seed与Reseed的处理,则与GAC的理念中所述一致,通过TileSource对外提供拓展。也就是默认情况下,Cache模块只提供Seed与Reseed的声明,无法提供具体实现(无法直接与Source进行链接)。 那么此刻只剩下最后一个需求了,其主旨围绕快速配置、易用。在此基于GeoWebCache中的DefaultStorageBroker 类进行缓存存储对象的代理,其符合GWC中缓存存储对象设计架构,也为后续提供了更多的拓展点,同时将其暴露给全局,即全局共用的缓存存储对象。对于快速集成能力,此处将结合Spring Boot的AutoConfiguration特性,为GAC提供自动装配能力。与此同时,将缓存存储对象的可设置属性通过配置的方式暴露出来,可直接在application.yml或application.properties中进行配置。具体可配置内容如下所示: geo-atlas.cache.enabled=false(是否启用缓存,默认为false) geo-atlas.cache.inner-caching-enabled=false(是否启用内存缓存,默认为false) geo-atlas.cache.inner.storage.provider=guava(可选值:guava,暂不支持,保留) geo-atlas.cache.inner.storage.memory-limit=16(内存大小限制,单位MB,默认16) geo-atlas.cache.inner.storage.concurrency-level=4(缓存并发级别的默认值,默认为4) geo-atlas.cache.inner.storage.eviction-policy=null(缓存驱逐政策,即缓存淘汰算法,可选值:NULL、LRU、LFU、EXPIRE_AFTER_WRITE、EXPIRE_AFTER_ACCESS,默认值为NULL) 💡 LRU、LFU暂时不支持...
前言 书接上回,本章节为下篇:TileMatrixSet实现及相关计算原理探讨。 本章节将以TileMatrixSet模型的典型实现,即GeoWebCache中的Gridset作为开端,对OGC中TileMatrxSet模型进行印证。而后对TileMatrixSet相关计算原理进行探讨,并以CGCS2000切片方案为例进行验证。另附带说明TileMatrixSet在Geo Atlas中的实现及应用 🤨。 Gridset & TileMatrixSet 这里就不再对GeoWebCache做介绍了,直接切入主题。GeoWebCache中的Gridset正是对应着TileMatrixSet模型,我们先来看一下GeoWebCache对于Gridset的相关介绍: Gridsets and Gridsubsets Gridsets 和 Gridsubsets 是指 GeoWebCache 所服务的图层的空间参考系统(the spatial reference system)。从本质上来说,正如 Tiles 中所介绍的,GeoWebCache 与参考系统无关。当 GeoWebCache 向 WMS 发出请求时,它使用 Gridsets 和 Gridsubsets 信息将其内部切片索引转换为 WMS 可以理解的空间请求。 💡 说实话,有时候我觉得老外的啰嗦话挺多的,其实这里就是表达GeoWebCache就是使用 Gridsets 和 Gridsubsets 将瓦片坐标转换为瓦片对应的空间范围的。从其实现来看,此处所述的内部切片索引正是对应着瓦片坐标系。 下面分别对 Gridset 和 Gridsubset 的构成进行描述,此处引用原文: A gridset is a global definition (i.e. not layer-specific) specifying: 全局定义即对应着通用的切片方案,所以不是特定于层(图层)的。 A spatial reference system (EPSG code) 对应着投影坐标系。 A bounding box describing the extent, typically the maximum extent for the above reference system...
前言 其实,在此前矢量金字塔技术研究一文中已经大致提及了瓦片金字塔与TileMatrixSet的关系,为什么还要在这里再次说明呢?主要是前文更侧重于矢量金字塔的概念定义,对于TileMatrixSet的描述过少,所以才会再次对TileMatrixSet进行说明。本文将基于 OGC Two Dimensional Tile Matrix Set 标准对TileMatrixSet进行研究探讨,将侧重于说明其基本构成要素的定义以及整体的技术实现。而GeoWebCache中的GridSet作为典型实现,本文将会对其进行再次说明,以印证OGC 关于TileMatrixSet的定义。由于内容较多,本文将分为下上两个篇章进行描述。上篇是对TileMatrixSet以及其构成要素的基本概念进行描述,而下篇则是对GeoWebCache中的GridSet实现进行研究,同时结合我国CGCS2000切片方案对TileMatrixSet格网划分计算原理进行探讨,另附带说明Geo Atlas中关于此模块的实现细节。 本章节为上篇:TileMatrixSet及其构成要素的基本概念。 什么是TileMatrixSet? TileMatrixSet,即Tile Matrix Set,源自 OGC Two Dimensional Tile Matrix Set(目前已更新到v2)。该标准定义了瓦片矩阵集的规则和要求,作为一种基于一组规则网格对空间进行索引的方式,这些规则网格为坐标参考系统中有限比例列表定义了一个域(瓦片矩阵)。 💡 The OGC Two Dimensional Tile Matrix Set and Tile Set Metadata Standard defines the rules and requirements for a tile matrix set as a way to index space based on a set of regular grids defining a domain (tile matrix) for a limited list of scales in a Coordinate Reference System....
Geo Atlas 是什么? Geo Atlas,译为地理地图册(地理地图集),就像小时候买到的纸质的地理地图册书本,里面填充着各式各样的地图。所以, 我也希望有那么一个东西,同样可以对外提供各种各样的地图以供使用。 目前来说,他还只是一个基于Java的开发的,可用于快速构建矢量切片服务的基础库。 本例中基于Geo Atlas实现了一个精简版本的矢量切片服务,从结果来看,可以将其看作为仅实现了矢量切片功能的GeoServer的精简提升版本。 背景与动机 首先,我是一个主做服务端开发的GIS开发工程师,平时接触最多的就是管网地图服务发布。此前的工作中没有使用任何平台技术,比如:ArcGIS、超图等,而是使用开源技术。技术栈大体可以总结为:SpringBoot + GeoServer + Mapbox + 空间数据库。 其次,目前在业务应用上,二维地图还是主流。而二维地图技术里面,属Mapbox Vector Tile的体验最好。所以,目前的技术路线是通过 GeoServer 来发布Vector Tile。 然后,在 GeoServer 使用中,我发现了这个几个问题: 受限于其开源协议(GPL 2.0)的约束,你无法通过修改部分源代码的方式,将其直接集成到系统内部。只能是单独部署,通过其提供的REST接口进行交互。基于此,部署方式同样受限。 GeoServer 是一个大而全的东西,同时他是一个单体项目。也就是说,哪怕我只需要提供MWTS+MVT服务,我也需要部署一个全功能的节点,无法按需使用(这里不得不提一下GeoServer Cloud项目,但是它提出得要求更多了,需要一套微服务的环境) 从公谨《WebGIS数据不切片或是时代必然》一文推论,GeoServer中提供的MVT技术(指数据切片过程)已经算是切片起源时代的产物了,而今已经跨过了矢量切片时代(数据还是预切),进入了动态矢量切片时代了 GeoServer中Vector Tile与GeoWebcache中的Tile Meta技术有冲突,瓦片清理存在BUG。可以理解为,GeoServer对于矢量瓦片的支持并不是很好。 不能很好的处理瓦片缓存与动态业务数据的矛盾,即使GeoWebcache提供缓存清理的策略。(Layer中Boundingbox范围问题) GeoServer完全是栅格金字塔技术的实现,不过是将栅格金字塔技术同时应用在栅格数据和矢量数据上,同时他并没有很好的针对不同层级对数据进行抽稀简化(其尝试从服务端配图SLD中获取数据分级规则,我没有测试过,但反对,理由参见: 关于矢量瓦片技术支持前端渲染带来的思考),那么就出现了一个pbf有近20M的情况。同时也回答了为什么现在大家都直接在数据库层面实现矢量瓦片(或者说是在数据源中),一是可以无视数据传递的时间损耗;二是可以直接做抽稀简化,这样出去的数据少了,传输速度自然也快了;三是数据库空间支持已经很成熟了;四是门槛高啊(护城河) 最后,我也想对我目前的状态做一次总结。那么,Geo Atlas应该就是我最好的总结方式。因为它既可以丰富我的简历🫣,又可以帮助我绘制技能树,完成这一次的总结。 ps: 当然,还有当下信创的背景原因。就当,抛砖引玉了😧,哈哈哈😬 特性 遵循 OGC Two Dimensional Tile Matrix Set and Tile Set Metadata Standard 2.0 [并不完全遵循] 尝试遵循 NEW OGC API 提供矢量切片能力 支持自定义数据属性分级规则 支持Google瓦片坐标系(原点在左上角, 默认即为Google瓦片坐标系) 支持3857(900913), 4490投影(即默认提供相应的TileMatrixSet) 支持自定义坐标系及自定义坐标转换行为(源数据坐标系) 支持自定义数据范围(OGC TileMatrixSet Limits,拒绝范围外请求) 提供全局统一的,可快速集成的瓦片缓存组件, 支持基于内存和文件系统的缓存 支持使用GeoPackage进行缓存 支持Seed, Reseed, Truncate三种瓦片缓存处理策略 提供Namespace, Datastore, FeatureLayer元数据管理模块,并提供一个可视化操作界面(Geo Atlas Dashboard) 提供栅格数据切片能力 提供地形数据切片能力 提供按需快速集成能力(将常用功能封装为各种stater) 截图 快速开始 以下说明旨在基于Docker技术快速搭建一个矢量切片服务示例。...
前言 在图像切片时代,多层次模型依靠的是影响金字塔。得益于影像栅格数据分辨率的特点,基于影像金字塔可以较好的实现多分辨率模型。但是在矢量切片时代中,就无法直接从影像金字塔技术获利了,因为矢量数据不具有分辨率这个特性,而是采用矢量金字塔技术来实现多层次、多尺度模型。 影像金字塔(分层) 影像金字塔技术通过影像重采样方法,建立一系列不同分辨率的影像图层,每个图层分割存储,并建立相应的空间索引机制,从而提高缩放浏览影像时的显示速度。如下图所示的影像金字塔,底部是影像的原始最高分辨率的表示,为512×512图像分辨率,越往上的影像的分辨率越小,分别为256×256,128×128,顶部是影像金字塔的最低分辨率的图像64×64,因此这个影像金字塔共有4层,即4个等级的分辨率。显然影像的图像分辨率越高,影像金字塔的等级越多。 从给出的定义与图示来看,好像与我们目前使用的瓦片地图有一定的差别。是的,这是因为影像金字塔负责的内容仅仅是构建多分辨率层次,也就是每一层都是对应完整数据范围的一块数据,也就是我们常说的分层(栅格数据是均衡的,可以通过分辨率来作为尺度描述,所以多分辨率层级也就对应着多尺度层级)。 瓦片金字塔(分块) 节选自《高性能影像数据瓦片化关键技术研究-刘世永-2016》 要实现现如今使用的瓦片地图模型,还需要瓦片金字塔配合完成。 瓦片金字塔模型是当前应用最广的多层次地图数据组织模型,通过瓦片金字塔模型,前端在进行放大和缩小操作时,可以有效地减少数据读取的空间查询时间。通过只加载可视区域范围内的瓦片,可以减少数据加载量,降低网络传输压力,提高前端的数据可视化速度。 也就是说,瓦片金字塔实在影像金字塔的基础上,基于特定规格大小对每层影像进行切割。此操作也就是对应着我们常说的分块。 数据原始分辨率并不是标准化的,分层结果即不够标准化,也不够细致。所以在此基础上再次分片,既是减少了数据量使得传输与加载效率提升,同时也是给出了标准,兼容性更好。 瓦片金字塔的主要原理为:基于某个特定的地图投影坐标系(常规是Web墨卡托),将曲面的地球投影到二维平面,而后将该二维平面进行多尺度地划分,即相当于制作了多个不同分辨率层级的数字地图。各层级对应相应编码,层级越高地图所对应的分辨率越高;而后对每一层级的全球空间范围地图按照某种空间划分方法进行格网划分,划分成若干行和列的固定尺寸的正方形栅格图片,这些切分出来的规整的单个格网单元称为瓦片,各层级的划分方法都是相同的。 瓦片划分方法需满足以下条件: 每个层级下的所有瓦片可以无缝拼合成一张全球空间范围的世界地图 每个瓦片都有唯一编码,根据编码可以解算该瓦片对应的空间范围 在某一层级下给定一坐标点可以根据其空间坐标解算其所在瓦片的编号 每一层级瓦片对应一层金字塔分层,所有层级的瓦片便构成了整个瓦片金字塔模型。每一层中的瓦片划分方法一般采用均匀四分的划分方法,即以赤道和中央经线的交点为初始中心,不断地对地图进行四分,直到每个格网的大小为tilesize * tilesize为止,其中tilesize表示单个瓦片的边长。基于此种划分方法,第0层金字塔(金字塔顶层)用一个瓦片就能表示整张世界地图,第1层要用4^z个瓦片来表示整个世界地图,z为当前瓦片的金字塔层级。 瓦片投影坐标系 瓦片金字塔模型中的投影坐标系可以有多种,目前最广泛采用的是Web Mercator投影,它是Mercator投影的一种变体。 瓦片坐标系 所有瓦片的编码都是基于瓦片坐标系下进行的,瓦片坐标系的原点一般都在左上角或者左下角,TMS规范中是在左下角(GeoWebCache遵循该规范),但是现有的Google、Mapnik切片系统都是选用左上角作为原点,本文主要以原点在左上角的瓦片坐标系进行说明。 瓦片的编码方式如下图所示,层级用z表示,瓦片经线方向(指瓦片经度发生变化的方法,即东西向,东向为正)上编号为x,纬线方向(指瓦片维度发生变化的方向,即南北向,南向为正)上编号为y,因此每一个瓦片都可以通过一个三维元组(x,y,z)来唯一描述。 总的来说,如今我们所说的影像(栅格)金字塔大多指代的是影像金字塔与瓦片金字塔的结合体或是瓦片金字塔(默认含有分层),而不是单独指代分层或者单独指代分块。 矢量金字塔 影像金字塔是为栅格数据服务的,也是图像切片时代的核心产物。但是到了矢量切片时代后,由于矢量数据并不具备分辨率的特性,且矢量数据不同于栅格数据,它有着疏密不一致、分布不均与的特点,所以无法直接利用影像金字塔技术。不过瓦片金字塔是基于分层金字塔的基础上构建的,所以对数据类型并没有要求,在矢量切片中是可以直接复用的。总而言之,在矢量切片时代中,需要一个符合矢量数据特点的分层模型,作为矢量数据源与瓦片金字塔之间沟通的桥梁。同时考虑到应用上的兼容性,所以最终基于金字塔理论之上进行分层模型的定义,谓之:矢量金字塔。 对于矢量数据的分层,将使用比例尺作为尺度描述,建立一系列不同比例尺的分层。不同于栅格金字塔的多分辨率层级,矢量数据金字塔没有分辨率的概念,但是不同层级之间的数据详尽程度也是不同的。随着比例尺的由小到大,矢量要素也变得越来越详细;而随着比例尺由大到小,矢量要素也将变得精简与概化,以符合人们的使用要求。 总而言之,矢量金字塔的的目的就是解决在小比例尺下大数据量(或高密度区域)矢量数据聚集度高、要素重叠和显示速度慢的问题。(其实就是矢量数据的制图综合问题,矢量金字塔不过是其中的一个解而已) 注:我们此处所谈矢量金字塔,只是矢量数据分层金字塔,只是分层。 矢量分层 为了达成人们的使用要求,则需要对数据进行处理,以符合给定比例尺级别下保持相应的详尽程度。 与栅格数据不同,矢量数据通常都具有空间特征与属性信息。空间特征体现在数据的空间坐标系、空间分布以及几何特征;属性信息则是数据实体相关的一些信息。那么矢量数据的空间特征与属性信息则可作为分层的依据,首要对数据的属性信息进行处理,而后再基于空间特征进行处理。 属性分类分级 对于数据的属性特征处理主要是对属性信息进行分类和分级两种情况。分类是根据属性信息划分类别;分级即根据属性信息按照其重要程度划分不同的级别,并且赋予不同的权重值。 空间特征处理 基于空间特征的处理则就多种多样了,比如可以基于数据的分布(密度)抽稀、基于周长或面积进行选取、基于几何形状进行简化。在处理的同时,还需要是实际情况考虑是否维持数据的拓扑关系。 总而言之,可将上述的分层处理方法抽象为两种: 选取(Filter):属性分类分级,基于密度、周长、面积的处理都属于选取 简化(Simplify):基于空间几何形状的处理属于简化 当然,上述两种只是最基础的分层处理方法,后续还可以有更多处理方法,比如合并、融合、夸大等。但我想来,如果能够比较合理的完成上述两个操作,应该也是达到了基本可用层次。 不同行业的数据具有着不同的重点或侧重点,分层不仅需要结合矢量数据模型的特点,还需要结合行业背景与应用场景综合考虑。 此处再借用公谨(遥想公瑾当年)的一句描述进行佐证: 所谓的矢量金字塔模型,即基于制图综合的知识,分别设置海量数据在不同zoom下是否显示,是否简化,是否融合的一种策略,当动态提取切片时,根据这个策略选择数据,实际捞取的数据就非常少,有效解决了矢量切片不能解决数据太密集集中的问题。 矢量金字塔 → 矢量数据的多尺度表达 → 矢量数据的自动制图综合(保证综合前后要素内部及要素之间的拓扑关系是矢量地图正确显示的基本需求 😯) 注:此处所述分层,并不是比例尺等级。而是在不同比例尺层级下,矢量数据的详尽程度。而分层处理,即为通过一定的手段来控制数据的详尽程度。 技术实现 在技术实现上,目前我看到的都是以瓦片金字塔结构为基础,叠加分层处理手段的方式实现的。因为瓦片金字塔是在分层金字塔的基础之上,而分层可分为两个部分: 尺度分级定义 → 一系列的比例尺等级 分层分级处理(综合算子) → 一系列的处理算子(如:Filter、Simplify) OGC TileMatrixSet 定义 而尺度分级定义不论是在分层金字塔还是瓦片金字塔中都是一致的,也就是将分层分级处理(综合算子)剥离出来单独实现,在最终的瓦片生产流程中,接入瓦片金字塔即可。 GeoWebCache 实现 瓦片金字塔结构 public class GridSet { private String name; // 投影坐标系 private SRS srs; // 瓦片宽, such as 256 private int tileWidth; // 瓦片高, such as 256 private int tileHeight; /** * Whether the y-coordinate of {@link #tileOrigin()} is at the top (true) or at the bottom * (false) */ protected boolean yBaseToggle = false; /** * By default the coordinates are {x,y}, this flag reverses the output for WMTS getcapabilities */ private boolean yCoordinateFirst = false; private boolean scaleWarning = false; // 将坐标参考系统 (CRS) 单位转换为米的系数 // 也就是说,这个参数表示的是给定的CRS中一个单元转换为米的系数。换句话说,也就是在指定的CRS中,一个单元表示多少米。 // 目前常用的就两种投影,一是以米为单位的(即metersPerUnit为1);其次是以度为单位的经纬度投影(metersPerUnit表示为1度代表多少米,即:360/赤道周长,不同CRS使用不同的椭球体,所以其赤道周长也会存在一定差异。) private double metersPerUnit; // 像素大小, 通常给定0....
前言 书接上回,此前提到地图瓦片切片技术的发展。矢量切片技术将瓦片的渲染由服务端迁移到客户端,此操作带来的影响力不可谓不大,基于此,完全可以随心所欲的定义地图的表达。那么在实际的应用当中,当渲染从服务端迁移后客户端后,是否会带来一些其他的问题? 超20M的瓦片数据 此事发生在2023年,当时我们的技术组合是:空间数据库+GeoServer(vector tile plugin)+ Mapbox GL JS,基于此提供矢量瓦片服务。某一天,在某位心细如发的大佬的察觉下,突然发现提供的地图服务在层级为10级时,出现一个大小大于20M的瓦片,顿感惊人。经过多次核对后,确定在该份数据下,GeoServer在小比例尺下(约莫10级往下)生产的大部分瓦片尺寸都比较大,而数据密度越大的地方尤其严重。最终,我们认为:是由于GeoServer没有提供矢量分层抽稀简化的能力,所以导致在小比例尺+高密度的双重叠加下,瓦片大小暴增。 在一段时间后,为了快速处理该问题,我曾提出:可以基于目前配图的思想(解析配图文件),在服务端去往数据库读取数据的时候便进行数据过滤(其实就是分层分级),避免大量不需要的数据经查询、传输、编码再传输带来的影响。比如我们按照口径进行约定,在13级往下,只显示大于xx口径的数据的数据,那么此时所有小于该口径的数据都不需要被传递。 但我们前端配图人员提出一个说法:说如果你这般处理,那么意味着配图的时候数据就不是完整的,我无法自行选择数据,不同的项目需求也不尽相同,灵活性自然大打折扣。同时,也就意味着该项配置与瓦片缓存是绑定的,一旦存在配图项的变更,便意味着此前缓存均是失效的。 配图的边界 时至今日,已过半年。目前想来,这里确定存在这么几个问题: 到底什么是配图,配图配的又是什么? 数据发布过程中,配图应该处于什么阶段? 对于什么是配图,我目前还给不出一个十分恰当的回答,因为此前确实没有做过这方面的内容。但私以为狭义情况下,地图的符号化过程应该称之为配图。配图内容如下: 要素符号化 文字标注 那么分层显示控制能力(属性分级)是否应该归属到配图中呢?在图像切片时代中,想来配图都是提前确定好的,而且受益于影像金字塔自带多层次分辨率的能力,所以在配图中压根不需要考虑分级问题(假定数据都是栅格数据)。但是矢量数据并没有分辨率的说法,所以无法从影像金字塔模型获利。在面对大数据量或高密度区域的情况时,需自行处理分级问题。 对于矢量数据来说,在矢量瓦片技术出现之前,所有瓦片都是在服务端渲染,那么属性分级控制也是在服务端进行的。而在矢量瓦片技术出现之后,完全可以直接在客户端实现属性分层控制了。且目前开源GIS技术方案大多都选择:GeoServer+Mapbox,但在实际使用中感觉GeoServer并不理会什么属性分级和空间特征简化,而是一股脑的将数据丢给客户端,客户端自己筛选。 从结果来看,好像在应用上确实是行得通的。实际上确实如此,或许大多数公司都是这样做的吧。但是我认为这是有问题的,这就好像是得益于矢量瓦片渲染能力后移的特点,服务端将原本应该自己管控的数据一股脑的丢到了客户端,由客户端自行控制。 那么问题来了,数据的分层分级控制权到底应该给谁,是瓦片生产端还是客户端呢? 我认为应该优先厘清整个生产流程,明确各节点能力边界 数据的分类、分层肯定是在最前面,且分类、分层与金字塔分层息息相关。且应该形成公知,瓦片的生产端与应用的客户端都应该清晰的知道【数据分层分级控制策略 → 矢量金字塔分层规则定义】 其次是矢量数据的发布,此中应该完整的实现矢量数据分层,即形成矢量金字塔结构【矢量金字塔模型实现】 因矢量瓦片技术带来的配图后置,数据的符号化在此处完成【地图符号化】 其次,可以想一下数据分层控制权限归属到客户端后会带来什么样的影响: 在大数据量或高密度区域的情况下,小比例尺级别瓦片尺寸可能爆炸 计算资源、存储资源(缓存)占用增多 对带宽要求高,移动端的话流量嗖嗖的跑 客户端性能下降 所以我认为,数据控制能力应该归属于瓦片生产端,客户端可以支持分层控制能力,但其对于数据的控制只能在全局统一的分类、分层策略所提供的范围内活动。也就是说客户端可以实现的分层范围是全局规定的范围的子集。 对于目前在配图中进行全局数据的分层分级行为,我认为是矢量瓦片技术带来的配图动作后置产生的影响。同时在GeoServer + Mapbox 客户端控制数据操作的长期影响下,给后来人一种错觉便是数据控制也变更到客户端控制。 论GeoServer的正确使用? 最近在自己实现一个矢量瓦片服务,由于自己目前只会写Java,所以不得不对GeoServer进行借鉴,所以就进一步的研究了GeoServer的源码。 GeoServer可以看作是GeoTools,GeoWebCache以及OGC API Implement的结合体。其中,GeoWebCache提供了金字塔结构的定义和缓存能力;GeoTools提供了空间数据相关的定义和操作。在本次研究后,我确定我需要向GeoServer道歉。在此前的描述中,我认为GeoServer是没有提供数据分层和空间简化的能力的,但是我错了。 我拉取的是GeoServer的2.22.x分支,因为这个版本用的比较多,相对而言更具有代表性,所以没有选择最新版本。 空间简化能力 也就是说,在GeoServer vector tile的生产过程中,已经集成了Simplify功能。 还有一个情况,其实GeoServer Vector Tile Plugin采用的矢量瓦片编码器(java-vector-tile)中其实也提供了Simplify的功能,只不过默认是关闭的。 数据分层(属性分级) GeoServer的vector tile实现中,居然是尝试从Style(SLD)中获取到Filter的信息,用以减少数据的检索(数据库中的Filter比Java基于内存的Filter更高效),同时还提供了一个将Mapbox style转换为SLD的拓展模块。 看到这里的我很激动,感觉找到了知己一般,哈哈哈。 在明白真相之后的我,只能是感叹GeoServer的历史包袱太重了,但应该被敬佩。 而此时再次回头看我之前提出解析配图文件的想法,发现这种做法也是有问题的。 矢量瓦片技术出现,实现了一套瓦片数据可以有N种配图方案 矢量瓦片技术完美的分离了数据与渲染,边界清晰 所以,基于配图剥离属性分级的方式不就又将渲染与数据耦合起来,配图也就会和缓存绑定了,也就是一种配图方案一套瓦片数据了(若一旦涉及到分级部分的变化),自然是有问题的。 结论 综上所述,私以为: 在矢量切片技术下,数据当有数据本身的分层规则,不应该依赖于配图,也不应该由配图来定义。 分层控制当属于数据控制权限,应该归属于瓦片的生产端控制 在矢量瓦片时代,配图就是地图符号化的过程,对应矢量切片技术下,渲染后移到客户端的部分 全局分层分级策略应该是首先定义的,且应该形成公知的形态。服务端的数据控制应当严格遵循该策略,客户端可控分层范围是该策略的子集 参考 GeoServer Branch 2....
前言 本文80%内容节选自:《WebGIS数据不切片或是时代必然》,后在其基础上添加了部分内容。 数据切片是解决大规模大体积地理数据在Web前端轻量化传输和显示的关键技术,是每一个开发者几乎每天都在使用的技术,有时候将服务端底图切成xyz图片,有时候将大影像数据切成xyz图片,也有时候将矢量数据切成xyz的矢量切片。 数据切片起源–图像切片时代(WMS → WMTS,服务端渲染+预处理) Web上古时代,人们浏览在线地图,一般是服务器端将页面地图要显示的地理范围内的地理数据都查询出来,然后在服务端按照专题地图的配置样式,渲染成地图图片,再返回客户端浏览,这个时代诞生的OGC标准就是WMS(Web Map Service)服务,该服务一直沿用至今。 类似WMS这种服务存在很多致命的缺点:由于地理范围的不可控,获取范围内的数据不可控,数据有时候会很大,数据通常在数据库中,IO和网络传输就耗时很严重;服务端获取数据后会进行数据渲染成地图图片,占用大量CPU资源;克服一系列的困难终于成图传输给前端浏览。可以想象,这个过程如此艰难,用户可能等的花儿都开了。 为了解决这个痛点,谷歌地图开创性提出了基于Web墨卡托投影,预先在服务端全量渲染,然后按照地图不同的显示级别(金字塔原理),切成了xyz的图片。 该技术成为Web2.0时代使用最广泛的技术,成为WebGIS标准,诞生了一系列如基于工业标准的TMS服务,基于OGC规范的WMTS服务。很快,百度地图,高德地图、天地图都使用该技术原理建设了自己的切片地图服务。广大GIS从业者也开始了“项目一启动,先把底图切”的套路。 基于该技术原理,对大的影像数据如几十G的GeoTiff数据也进行了切片,形成影像底图给客户端使用。 这个时期的开源GIS主要技术是基于GeoServer的WMS、WFS、TMS、WMTS服务。 核心技术:Web墨卡托投影、栅格金字塔 数据切片发展–矢量切片时代(WMTS,客户端渲染+预处理) 虽然基于XYZ的图片切片技术很成功,但是也存在不少问题: 全图预切耗时长:通常切图zoom级别从0到20,通过金字塔原理图可知,上一级别的一张图片,下一级别会裂变成4张,级别越大,图片越多,同时地理范围越大,图片也越多,图片多了,切图耗时就很长,资源无论是基础底图切片还是影像底图切片都要耗时漫长。 资源要求高:需要庞大的服务端渲染和切图的计算资源,满足分布式渲染、切图、存储需求。 存储冗余:原始矢量和栅格数据,同时冗余存储了数据切片,存储压力大,数据切片转储IO压力大。ArcGIS为了提升转储性能,开创了离散性切片和紧凑型切片两个格式,紧凑型切片在转储时性能较高。 数据更新不及时:由于切图耗时长,通常很少会定期更新地图切片数据,导致数据显示落后于实际生活现状,不能很好的服务用户。 地图千篇一律:这个时期项目最常采用的是天地图地图服务,不管啥项目,不管什么地区,似乎地图都长一个样子。甲方可能审美疲惫了,他们希望底图能是定制化的,能不能暗黑一点?科幻一点? 既然存在问题,自然有人想解决问题,从15年左右,Mapbox受够了传统地图显示风格了,它提出了MVT矢量切片技术,该技术一经推出就大受欢迎,开启了WebGIS底图定制化时代。 很显然,矢量切片和图像切片最大的区别就是:渲染从服务端迁移到客户端了,换句话说,服务端减负了,带来的好处也是不言而喻: 资源要求降低:服务器资源要求可见性降低,渲染很耗资源的。 数据更新比较及时:如果数据更新,简单更新下局部变更地区的数据的矢量切片即可,由于不用全图渲染,耗时极大减少,数据的有效性极大提高。 存储冗余降低:采用MbTiles的设计规范,将数据和序号索引剥离,复用重复区域数据,极大降低切片数据量。但是数据冗余还是存在的。 地图不再千篇一律:渲染是客户端的,那么甲方各种优秀的创意都能得到释放了,配置大屏的感觉已经是很容易的事情了。各行各业的地图五彩斑斓各有千秋,地图设计者们的春天来了。高德、百度跟的很快,目前也都提供矢量切片底图定制化服务了 这个时期的开源GIS主要技术是基于GeoServer的矢量切片服务,基于Mapbox的底图切图工具tippecanoe等。这个时期,3D GIS蓬勃发展,诞生了3DTiles、I3S等3D切片ogc规范,并大量在工程中使用。 矢量切片解决了不少问题,但仍遗留问题是:全图预切耗时长,切片数据冗余占用存储空间。这个问题同样也是栅格切片、3D切片共同的问题。于是有人问,能不能数据不切片?也就不存在预切耗时长,切片数据冗余的问题了?答案是肯定的。 核心技术:矢量金字塔,MVT 正所谓尾大不掉,虽然已经出现了矢量切片技术,但是整体的工艺流程还是在预切路线。这可能和矢量切片解决的问题相关,因为矢量切片解决的是渲染的问题,可以更自由的进行渲染了,而且只需要提供一份切片数据即可。 数据切片方向–动态切片时代(WMTS,客户端渲染) 那么上一个阶段遗留的问题还有什么:预处理,也就是不管怎么样,我先切数据 笔者(原文作者:遥想公瑾当年)曾经大量使用PostGIS+并行计算+动态矢量切片技术实现动态业务数据的快速前端矢量呈现,PostGIS是目前开源架构里唯一能支持动态矢量切片的数据技术。 动态切片技术不再预先切图,也不会有大量切片的文件存储,将切片技术诞生以来所遗留的问题都一次清空。由于通常数据量很大,动态矢量切片技术基本上数据不出库,而由数据库汇总组织数据并直接生成切片结果出去,后台几乎啥也不做,时空数据库的地位越来越重要。 这是一个崭新的时代,预示着数据不切片时代的来临,毕竟矢量和栅格不切片技术理论上还是比较成熟的,未来3DTiles这些3D切片理论也会逐渐成熟的。 核心技术:动态矢量切片+矢量金字塔+MVT 在这里我想要补充一下上面的示意图,这里不是说动态矢量切片技术只能应用在数据库中,前文中也提到当前开源架构中PostGIS是唯一能支持动态矢量切片的数据技术。这里是基于传统矢量切片技术与PostGIS的动态矢量切片技术的对比,更多的是想表达流程的变化(预切 → 动态,以及切片产生的位置),以及流程变化带来的性能提升和相关影响;还有就是原文作者认为的后续的发展方向,Based on spatial database。 关于动态矢量切片技术 动态矢量切片技术可以说是传统矢量切片的动态应用(更广义的可以认为:是可以按需动态生成的,具备多层次模型,且每个层次包含适当选取及简化的数据的切片技术)。强调不再预切,而是按需生成,且应该同时满足可以快速显示的要求。 按需动态生成(与数据源是链接着的或是可链接的,即可达成随时可访问),不做预切 多层次结构(如:矢量金字塔) 数据选取与简化 MVT支持 从发展进程上面看,技术的升级是为了解决此前存在的痛点问题。动态矢量切片技术是为了解决预切耗时,且会产生大量的瓦片存储(预切),数据越大,产生的瓦片越多。 从实际的应用上看,此前的矢量切片技术我更愿意将之称为传统矢量切片技术,用以与矢量切片做一定的区分。传统矢量切片技术还是走的预处理的路子,产生的是静态矢量瓦片数据,即在流程上是无法做到快速更新的。与之对应的便是动态实时生产的路线,可称为动态矢量瓦片技术。私以为,两者更大的区别在于矢量瓦片生产的思路,也就是是否需要预切。 所以,动态矢量切片技术可以有各种各样的实现,而PostGIS中的动态矢量瓦片技术便是其中的一种。且到目前,GeoServer同样实现了动态矢量瓦片技术(只不过其历史包袱过重 🫡)。 值得注意的是,基于数据库的动态矢量切片有一个很大的特点:缩短了切片的传导路径(也就是所谓的数据不出库,出库即为切片)。 参考 WebGIS数据不切片或是时代必然 PostGIS动态矢量切片(原理+实现)
起源 天气小程序产生于2022年年初,目的是用于验证自己是否有进入全栈开发(仅前后端)的能力。 受新冠疫情影响,2022年的春节是在杭州过的。还记得当时附近好几个地方都被划为了高风险,对整个区进行了管控。如果选择回家的话,得到将是14天的隔离,还不确定能否回来上班。因此便没有回去了。好在所在的区域情况并没有那么的严重,还是可以去买菜的。领了消费券,再加上公司发的年货,也没有想象中的那么糟糕。 所以,既然没有回去,又有十来天的时间,总得做点什么东西才行,对吧 🙄! 2022年,是我工作的第四个年头。受多方面的信息影响,我也想看看验证自己是否有进入全栈开发(仅前后端)的能力。 历程 拂衣天气,又名微天气。 一个集地理信息与天气预报为一体的天气预报类小程序,界面精美,使用便捷。【致敬:和风天气】! 💡 主要是有人给我说拂衣天气,听着还以为是卖衣服的。so,我就想着换个名字,就有了微天气。但是由于微信认证的原因,所以有些地方还是拂衣天气。现在细细想来,好像也没有什么关系,那就先这样吧 🙄 该项目从2022年1月12号正式启动,于2022年3月19日发布一阶段最终版本(1.1.9),总体耗时2个月零7天。从内容完整度以及界面友好程度来说,我给自己70分。 此前实在是没有经验,也没有相关的习惯,天气小程序开发过程中并没有编写相关的文档。所以在小程序开发完成之后,本来计划是将拂衣天气完整的开发过程通过文章的方式记录下来,并将该项目开源出去,甚至还想将该项目提交给和风天气。但最后的结果就是:文章就完成了四篇,也就是一个开头,没啥实际内容。@time 2022年8月16日 好的,现在时间来到2023年12月,我又有点时间了,因为我离职了(不是被裁)。这次的目标是完善文档,修复发现的一些BUG,然后新增一些内容,最终将该小程序开源,并贡献给和风天气。 接下来,让我来回忆一下每个阶段的详细内容 🤔 项目初始 为什么会想到做一个天气小程序呢? 嗯,首先我是一个做服务端开发的GIS开发工程师。在当时刚结束一个小程序的开发工作,觉得小程序这个东西还挺有意思。同时受到周边各种信息的影响,也想试一试写点前端的内容,最好是可以方便发布的那种,也算是自己的作品不是。为什么会选择天气类别,好像是当时刷网页还是什么,看到一个人分享自己做了一个天气机器人,然后给女朋友推天气信息。so, 🫣 所以,我当时就想做一个天气类别的小程序,以此进行全栈开发能力的试炼。我想这会是一个微信小程序、是一个可以正常使用的小程序,以Java进行服务端开发,以Mapbox实现天气数据可视化。 本阶段事务分为了三个阶段,分别是:调研、学习、实现 调研 天气小程序什么最重要?当然是天气数据最为重要,所以首要内容便是确定天气数据的来源。其次便是确定本次天气小程序的技术实现构成。 天气数据 天气数据需要是真实的、可用的。那么可以通过网络中提供的天气API进行获取。 通过一定的检索后,我选定了两个天气平台,分别是:和风天气、心知天气。 高德天气:大平台,但是目前服务类目比较少 彩云天气:免费接口几乎没有,收费又太贵 心知天气 心知天气试用版与开发者版开发产品几乎等同,且开发者版收费也不贵。最为关键的是,支持以经纬度方式进行天气查询。 和风天气 几乎可以免费使用其提供的所有 API,且同样支持经纬度方式进行天气查询。 对比了这两者,发现至少都需要注册为开发者之后,才可以较好的使用其服务。且两者的开发者认证均需要实名。 关于天气API的选择,我最终选择了和风天气,倒不是因为它可以免费使用。其实,刚开始的时候我更倾向于使用心知天气,因为它还可以直接查昨天的天气(和风对于历史天气的查询比较麻烦)。但是和风天气首页结合了地图进行可视化,而且还提供有APP可以使用(方便参考)。再加上,我想了想,其实我并没有迫切的需要知道昨天的天气情况。 🙄 💡 其实最重要的原因在于:我先注册了心知天气(需要审核),过了半天后再去注册了和风天气(需要审核),但是最先通过审核的是和风天气(耗时大概也就半天左右,我是在春节期间注册的啊)。 技术实现构成 这里存在一个遗憾,小程序原生并不支持使用如mapbox这样的第三方地图框架,初始想法是通过webview的方式使用mapbox,但是遗憾的是,webview并不对个人类型的小程序开放使用。 所以,退而求其次,选择腾讯地图(及其提供的样式)实现地图浏览。 服务端程序则使用Java语言开发,天气数据是经过服务端代理的方式形成内部的数据接口,并非是小程序直接调用和风的接口 最终将服务端程序部署到阿里云 学习 我是一个后端开发工程师,我不怎么会写页面,我特别的讨厌写CSS。我也没有接触过前端开发和微信小程序开发,所以需要提前储备一下相关知识。 前端知识 我并没有想要精通前端技术,但是我需要比较体系的了解一下前端技术,方便进行小程序开发。所以我在B站找了两门前端视频学习(粗略的刷了一遍) 尚硅谷Web前端零基础入门HTML5+CSS3基础教程丨初学者从入门到精通 千锋web前端开发项目教程_1000集完全零基础入门HTML5+CSS3+JS到精通 微信小程序知识 其实就看官方文档就足够了,不过心虚的我还是在极客时间找了门课。最终发现:就官方文档就足够了,因为我并不需要很深入的东西 实现 在有一定准备后,就开始进入实现环节了。 在这里有一个大问题就是UI设计问题,我看和风天气APP就挺好看的,有天气有地图,身为GIS开发的我就很喜欢。又发现无论是和风天气官方,还是其他天气小程序应用,基本都没有携带地图的。 所以,我当即决定参考和风天气官方APP界面,做一个类似的天气小程序。当然,我做的这个小程序就是奔着开源,同时将作为作品分享给和风天气,才想着如此操作的。请务必慎重 🤔 在经过简单的设计过后,于2022年1月18日正式进行开发,2022年3月19日发布一阶段最终版本1.1.9。 文档补充 今天偷的懒,明天都会让你加倍还回来。此前一是没意识,二是认为没必要,三还是太懒,所以并没有同步编写文档。 现在计划将拂衣天气开发的完整过程通过文章的方式记录下来,下面是我对该整体内容的编写计划: 但是,我又要开始说但是了。打工人还是打工人! 天气小程序于2022年4月前就已经完成了开发,直至到今天(2022年8月16日)也就才完成了三篇文章,不得不说拖延症是真的严重。天气小程序只是一个应用,就目前的投入收益来说,不应该把过多的时间放在小程序上面。在加上距离开发已经过去了4个月,开发之时并没有进行文档产出,所以现在才进行复盘则是相当于重新实现了一遍。就目前来看,核心的行政区划数据合并已基本完成,所以,后面将暂停小程序相关内容,变更为GIS基础与计算机基础的学习。 💡 对的,停更说明。 死灰复燃 打工是不可能打工的,只要我不打工,我就有时间了 🙄...
前言 服务端部署:由于并没有建立全链路的自动化部署,目前还需要到云服务器上进行环境制作(数据库,Nginx),并拉取后端服务进行部署 小程序发布:需要先完成服务端部署,保证应用正常可用 服务端部署 数据库安装与数据初始化 最开始的时候,我是直接将在操作系统上面安装数据库,后面发现迁移的时候还是不方便,即使我可以放弃数据库中的数据,但是还是需要重新创建数据表结构。 所以,在这一次中,我编写了一个Dockerfile脚本,使用一个空数据库作为模板,构建了postgis镜像。基于此,我可以实现快速的迁移与部署。由于这是一个实验性质的小程序,所以数据资产并不是十分重要(并不是说不安全,而是我可以丢弃),所以我可以安心的使用docker技术。在初始化postgis容器的时候,会同步释放模板以创建数据库。这将会带来一个问题是:如果重新创建容器,那么将会得到一个全新的数据库。当然你可以把数据库的data目录映射到宿主机上,应该可以解决这个问题。 docker build -t registry.cn-hangzhou.aliyuncs.com/fuyi-atlas/micro-weather-postgis:12-3.4 . 最后,我在本地完成该镜像的制作后,将其推送到我的阿里云镜像仓库中,便于后续使用。你可以注册一个阿里云账户去免费启用个人版的镜像仓库,也可以直接使用dockerhub。 docker push registry.cn-hangzhou.aliyuncs.com/fuyi-atlas/micro-weather-postgis:latest 应用部署 于本地完成镜像制作,并推送到阿里云镜像仓库中。 为了更方便的进行部署,我编写了docker compose脚本,用于将数据库与应用服务端程序一并启动。目前程序中有一个海报分享的功能,该功能实现中需要一些额外的中文字体的支持,所以需要将此部分中文字体放置到宿主机的某个目录下,并在环境变量中指定该目录,脚本中会将该目录映射该到容器内的/usr/share/fonts目录下 对于图片访问,还是延续此前的实现,即在服务器端生成分享海报,存储到本地(指服务器磁盘),而后通过Nginx代理访问。由于目前的服务器我是与他人共用,同时还需要配置域名证书,所以暂时没有将Nginx的部署同步放置到docker compose脚本中。 💡 由于部署中需要提供部分敏感信息,比如:小程序的密钥、天气应用的key、docker镜像仓库地址。我将所有信息以环境变量的方式进行占位,通过docker compose的环境变量进行替换,即.env文件 小程序发布 小程序端的发布就比较简单了 先将base_url更换为正式环境的地址(我没有提交此部分代码,你可以自己更改) 本次调试没有问题后,就可以上传代码,即提交为体验版本 而后使用体验版本测试没有问题,就可以提交审核 审核通过后,就可以发布了 总体来看,本次发布很顺利,审核在十分钟就通过了。
前言 这里暂不作过多的操作,还是保持与此前一致。即通过Github Action完成Docker Image的build与push,目标仓库为阿里云容器镜像服务实例(个人版)registry.cn-hangzhou.aliyuncs.com 那么一共分为三个部分: Dockerfile编写 阿里云容器镜像服务配置 Github Action Dockerfile编写 jdk17 gradle FROM gradle:jdk17-alpine AS build # 设置语言,支持中文 ENV LANG C.UTF-8 COPY --chown=gradle:gradle . /opt/gradle/src WORKDIR /opt/gradle/src RUN gradle clean build -x test --no-daemon FROM eclipse-temurin:17-jdk-jammy COPY --from=build /opt/gradle/src/build/libs/*.jar /usr/app/ WORKDIR /usr/app/ RUN sh -c 'touch micro-weather-backend-1.0.0-RELEASE.jar' ENTRYPOINT ["java", "-jar", "micro-weather-backend-1.0.0-RELEASE.jar"] Github Action 先在阿里云镜像服务中创建命名空间 创建仓库(可选,因为可以自动创建) 编写Github Action脚本 name: Micro Weather Service Image Build And Push CI on: push: branches: - 'main' jobs: docker: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 # setup-qemu 如果您想使用 QEMU 添加仿真支持以便能够针对更多平台进行构建,则 action 会很有用 - name: Set up QEMU uses: docker/setup-qemu-action@v1 # setup-buildx-action 将默认使用docker-container 构建器驱动程序创建和引导构建器。非必需 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 - name: Login to Aliyun DockerHub uses: docker/login-action@v1 with: registry: ${{secrets....