基于Java的GeoTools对Shapefile文件属性信息深度解析

news2024/10/6 1:33:59

目录

前言

一、Shapefile的属性列表信息

1、属性表格信息

2、属性表格包含的要素 

二、GeoTools对属性表格的解析

1、常规解析方法

2、基于dbf文件的属性信息读取

三、总结


前言

        ESRI Shapefile(shp),或简称shapefile,是美国环境系统研究所公司(ESRI)开发的一种空间数据开放格式。该文件格式已经成为了地理信息软件界的一个开放标准,这表明ESRI公司在全球的地理信息系统市场的重要性。Shapefile也是一种重要的交换格式,它能够在ESRI与其他公司的产品之间进行数据互操作。Shapefile文件用于描述几何体对象:点,折线与多边形。例如,Shapefile文件可以存储井、河流、湖泊等空间对象的几何位置。除了几何位置,shp文件也可以存储这些空间对象的属性,例如一条河流的名字,一个城市的温度等等。

        Shapefile属于一种矢量图形格式,它能够保存几何图形的位置及相关属性。但这种格式没法存储地理数据的拓扑信息。Shapefile在九十年代初的ArcView GIS的第二个版本被首次应用。许多自由的程序或商业的程序都可以读取Shapefile。Shapefile是一种比较原始的矢量数据存储方式,它仅仅能够存储几何体的位置数据,而无法在一个文件之中同时存储这些几何体的属性数据。因此,Shapefile还必须附带一个二维表用于存储Shapefile中每个几何体的属性信息。Shapefile中许多几何体能够代表复杂的地理事物,并为他们提供强大而精确的计算能力。
        Shapefile文件指的是一种文件存储的方法,实际上该种文件格式是由多个文件组成的。其中,要组成一个Shapefile,有三个文件是必不可少的,它们分别是".shp", ".shx"与 ".dbf"文件。表示同一数据的一组文件其文件名前缀应该相同。例如,存储一个关于湖的几何与属性数据,就必须有lake.shp,lake.shx与lake.dbf三个文件。而其中“真正”的Shapefile的后缀为shp,然而仅有这个文件数据是不完整的,必须要把其他两个附带上才能构成一组完整的地理数据。除了这三个必须的文件以外,还有八个可选的文件,使用它们可以增强空间数据的表达能力。所有的文件名都必须遵循MS DOS的8.3文件名标准(文件前缀名8个字符,后缀名3个字符,如shapefil.shp),以方便与一些老的应用程序保持兼容性,尽管现在许多新的程序都能够支持长文件名。此外,所有的文件都必须位于同一个目录之中。

        由于Shapefile有着广泛的适用性,是很多公共地理服务所采用的矢量文件规范之一。因此有必要对Shapefile文件进行深度的解析。如果将shapefile类比为一个数据库,那么它就是一个database,而其中的属性表则是一张一张的物理表。他们两存在天然的对应关系。虽然在前面的博客中,我们已经对shapefile的解析有了基本的认识。但是对属性表格的解析并没有介绍得很详细。本文则重点讲解在Java当中,如何使用GeoTools来进行属性表格的解析,首先在Qgis中重点介绍属性表格的字段信息,然后介绍一种常见的属性字段信息解析方式,其次介绍基于dbf的属性信息解析方法,直接解析dbf文件的方法在很多网络知识中很少见,但是却非常有用,比如在获取double类型的数值时,想获取精度值,就可以通过这种方式来获取。学习本文,不仅更加熟悉GeoTools对属性表格的解析,同时对于如何快速的将Shapefile的属性模型映射成空间库模型有更进一步的认识。希望对您有一定的帮助。

一、Shapefile的属性列表信息

        本文主要是讲解如何来深度解析shapefile的属性表,因此就有必要对属性列表信息进行一个详细的讲解。

1、属性表格信息

        shapefile文件其实是文件数据库的一种,它将数据的信息写入到shapefile文件中,然后在其它的电脑终端中可以直接共享这些数据,解析这些属性信息。为了介绍方便,我们将shapefile与常见的关系型数据库进行对比,可以这么进行理解。一个shapefile就是一个database,而一张表就是一个属性表。属性表格中的字段、数据类型、长度、精度等就是与关系型数据库中的数据列是一个意思,而数据行基本就是数据库中一行数据的意思。下面是以某年度某城市POI的矢量数据为例进行讲解,在Qgis中打开字段表如下:

2、属性表格包含的要素 

        既然与关系型数据库的数据定义类似,那么就有必要对属性表格的每一列的信息进行精准的定义。包括数据类型、数据长度、精度等,下面结合示例数据来进行一个深入讲解。

序号参数参数
1名称具体的字段名称,如name
2别名类比于关系数据库的字段别名
3类型数据类型,比如QString
4类型名数据类型名,比如String,Real
5长度字段的长度,比如:255
6精度double等类型的精度,如11
7注解其它辅助信息

        有了上面的信息之后,我们也可以根据属性表格来进行空间表的创建,这样管理起来也比较方便。在数据库中定义好每一个列的数据信息之后,就可以将具体的空间信息填充到每一行中,这时候可能就需要外业和内业的工作人员通力配合。

        在了解上面的基本信息后,在下面的章节中,我们就要进行属性表格的深度解析操作。 

二、GeoTools对属性表格的解析

        本节重点使用两种方法来进行属性表格解析的实战,第一种方法是常规的通过featureType的方式来获取属性表格的信息。第二种是基于GeoTools来读取dbf文件来获取元数据信息。这里不仅说明两种方式的优缺点,也说明两者的具体开发代码,帮助大家来深度掌握GeoTools的属性表解析操作。

1、常规解析方法

        首先讲解普通的解析方法,同时这种解析的方式在互联网或者很多博客当中是最常见的。也就是直接解析shp文件。具体的解析步骤流程如下所示:

        这里给出详细的遍历代码,如下所示:

 

public void testAttrLength() throws Exception {
		// 指定Shapefile的文件路径
		String shpFile = "C:/BaiduDownload/长沙市2020年POI数据集/长沙市2020年POI数据集/长沙POI数据(.shp)/风景名胜.shp";
		FileDataStore dataStore = FileDataStoreFinder.getDataStore(new File(shpFile));
		ShapefileDataStore shapefileDataStore = new ShapefileDataStore(new File(shpFile).toURI().toURL());
		System.out.println(shapefileDataStore.getCharset());
		// 打开数据存储
		String[] typeNames = dataStore.getTypeNames();
		System.out.println(typeNames.length);
		for (String type : typeNames) {
			System.out.println(type);
		}
		// 获取特征类型
		SimpleFeatureType featureType = dataStore.getSchema(dataStore.getTypeNames()[0]);
		CoordinateReferenceSystem crs = featureType.getGeometryDescriptor().getCoordinateReferenceSystem();
		System.out.println("坐标参考系统:" + crs);
		// 获取属性名
		List<String> attributeNames = featureType.getAttributeDescriptors().stream().map(attr -> attr.getLocalName())
				.collect(Collectors.toList());
		System.out.println("Attributes: " + attributeNames);
		System.out.println("以下是属性信息的深度解析:----------------------------------------");
		List<AttributeDescriptor> attrDescList = featureType.getAttributeDescriptors();
		System.out.println(attrDescList.size());
		for (AttributeDescriptor attrDesc : attrDescList) {
			System.out.println(attrDesc);
			System.out.println("属性详情:");
			AttributeType attrType = attrDesc.getType();
			System.out.println("userData=========>" + attrType.getUserData());
			System.out.println("数据库数据类型:" + convertShpFieldType2H2GISOrPG(attrType.getBinding()));
			System.out.println(attrType);
			System.out.println("name=" + attrDesc.getName() + "\tLocalName=" + attrDesc.getLocalName() + "\t"
					+ attrDesc.getMaxOccurs() + "\t" + attrDesc.getMinOccurs());
			List<Filter> restrictions = attrType.getRestrictions();
			System.out.println("过滤器为 : " + restrictions);
			for (Filter filter : restrictions) {
				if (filter instanceof IsLessThenOrEqualToImpl) {
					IsLessThenOrEqualToImpl impl = (IsLessThenOrEqualToImpl) filter;
					Expression exp1 = impl.getExpression1();
					Expression exp2 = impl.getExpression2();
					System.out.println(exp1.getClass());
					System.out.println(exp2.getClass());
					if (exp1 instanceof LengthFunction && exp2 instanceof LiteralExpressionImpl) {
						LengthFunction length = (LengthFunction) exp1;
						LiteralExpressionImpl literal = (LiteralExpressionImpl) exp2;
						System.out.println( length.getName() + ":\t" + literal.getValue());
					}
				}
			}
			System.out.println("**************************************************");
		}
	}

        可以看到,这里关于数据的属性列表信息,尤其是获取字段的长度和精度时,是通过在过滤器中的具体子类来实现的。在Filter列表中,一定会用到两个表达式对象,其中的一个是:IsLessThenOrEqualToImpl,另一个则是LiteralExpressionImpl,通过这两个类来实现长度的控制。因此如果需要在代码中获取具体的长度,可以通过这两个表达式对象来获取。同时,在进行数据的解析时,虽然我们在前面对两者进行了简单的类比,但毕竟两者不是一个东西,因此在进行数据的存储时,具体的设置还是有一定的区分的,比如对应的具体数据类型,这里我们来提供一个类来进行转换。

/**
* convert shpFileType to db field type
** 备注:目前arcgis的字段类型有:短整型 长整型 浮点型 双精度 文本 日期
* @param value
* @return
*/
public static String convertShpFieldType2H2GISOrPG(Class<?> value) throws Exception {
   if (value == String.class) {//文本
        return "varchar";
   }
   if (value == Integer.class) {//短整型
       return "int";
   }
   if (value == Long.class) {//长整型
       return "bigint";
   }
   if (value == Double.class || value == Float.class) {//浮点型 双精度 保留精度,比如在金币上运算更安全
      return "numeric";
   }
   if (value == Date.class) {
      return "TIMESTAMP WITH TIME ZONE ";//日期, 使用包含时区的来处理
   }
   if (Geometry.class.isAssignableFrom(value)) {
       return "geometry";
   }
   //除了上述,真不知道还会有啥了。除非arcgis的shp又增加了新类型?!那无能为力了,抛出异常吧
   throw new Exception("不支持的类型!" + value.getName());
   //
   //if (value.getSuperclass().getName().equals(String.class))
   // return null;
}

        在GeoTools中,关于Filter的相关类,非常的庞大,这里不全部展开,以防止抓不住重点。先给一个Filter的类图给朋友们,感兴趣的可以先了解一下。

        通过以上的步骤和示例代码,我们可以在控制台中输出以下信息:

        为了方便展示字段的解析,这里特意将复制一段处理的输出信息:

AttributeDescriptorImpl WGS84_纬 <Double:Double> nillable 0:1
属性详情:
userData=========>{}
数据库数据类型:numeric
AttributeTypeImpl Double<Double>
restrictions=[ length([.]) <= 19 ]
name=WGS84_纬	LocalName=WGS84_纬	1	0
过滤器为 : [[ length([.]) <= 19 ]]
class org.geotools.filter.LengthFunction
class org.geotools.filter.LiteralExpressionImpl
length:	19

        可以看到,通过上述的代码就可以获取属性表格的字段名、字段类型、字段的长度等信息,有了这些信息,我们其实可以做的事情很多了。不知道大家发现一个问题没有,在上述的代码中,并没有返回精度,即11位小数的长度。上述代码是无法实现的。那么有没有什么办法来实现精度值的获取呢。

2、基于dbf文件的属性信息读取

        众所周知,在shapefile文件夹中,除了shp文件之外,另外一个prj和dbf文件也是缺一不可的。既然我们无法通过读取shp来实现精度的精确读取,那么是否可以从这两个文件当中来获取呢?shp文件有我们定义好的空间信息,而大量的属性表格,我们是存放在dbf中,因此我们来测试读取dbf文件看能否找到另外的一条大路。

        幸运的是,在Geotools当中,官方提供了DbaseFileReader这个类,通过这个类实现对dbf文件的读取,之前也查找了一些相关的资料,但是很少有博主写这种实现模式。这里提供基于DbaseFileReader的实现方式,代码实现更简单,性能更好。其原理非常简单,通过DbaseFileReader来获取DbaseFileHeader,然后就可以遍历DbaseFileHeader中的信息,就可以实现所有信息的识别。代码如下:

 /**
 * - 读取dbf文件
 * @throws IOException
 */
 @Test
 public void testReadDbf() throws IOException {
	File dbfFile = new File("C:/BaiduDownload/长沙市2020年POI数据集/长沙市2020年POI数据集/长沙POI数据(.shp)/风景名胜.shp");
	// 打开 DBF 文件
	ShpFiles shpFile = new ShpFiles(dbfFile);
	System.out.println(Charset.defaultCharset().toString());
	DbaseFileReader dbfReader = new DbaseFileReader(shpFile, true, Charset.defaultCharset());
	// 读取 DBF 文件的头信息
	DbaseFileHeader header = dbfReader.getHeader();
	System.out.println(header.getNumFields());
	System.out.println(header.getNumRecords());
	for (int i = 0; i < header.getNumFields(); i++) {
		System.out.println(header.getFieldName(i) + "\t" + header.getFieldLength(i) + "\t" + header.getFieldType(i)
		+ "\t" + header.getFieldDecimalCount(i));

	}
	// 关闭 DBF 文件读取器
	dbfReader.close();
}

        这个类的方法如下:

Modifier and TypeMethodDescription
voidclose()

Clean up all resources associated with this reader.Highly recomended.

protected intfill​(ByteBuffer buffer, ReadableByteChannel channel)
DbaseFileHeadergetHeader()

Get the header from this file.

booleanhasNext()

Query the reader as to whether there is another record.

Stringid()

An id for the reader.

static voidmain​(String[] args)
voidread()

Reads the next record into memory.

Object[]readEntry()

Get the next record (entry).

Object[]readEntry​(Object[] entry)

Copy the next entry into the array.

Object[]readEntry​(Object[] entry, int offset)

Copy the next record into the array starting at offset.

ObjectreadField​(int fieldNum)

Reads a single field from the current record and returns it.

DbaseFileReader.RowreadRow()
voidskip()

Skip the next record.

voidtransferTo​(DbaseFileWriter writer)

Transfer, by bytes, the next record to the writer.

        想要获取属性就是通过获取Head对象即可实现属性列表的深度解析。返回DbaseFileHead,它的方法如下表所示:

Modifier and TypeMethodDescription
voidaddColumn​(String inFieldName, char inFieldType, int inFieldLength, int inDecimalCount)

Add a column to this DbaseFileHeader.

Class<?>getFieldClass​(int i)

Determine the most appropriate Java Class for representing the data in the field.

intgetFieldDecimalCount​(int inIndex)

Get the decimal count of this field.

intgetFieldLength​(int inIndex)

Returns the field length in bytes.

StringgetFieldName​(int inIndex)

Get the field name.

chargetFieldType​(int inIndex)

Get the character class of the field.

intgetHeaderLength()

Get the length of the header

intgetLargestFieldSize()

Get the largest field size of this table.

DategetLastUpdateDate()

Get the date this file was last updated.

longgetLengthForRecords​(int records)

Returns the expected file size for the given number of records in the file

intgetNumFields()

Return the number of fields in the records.

intgetNumRecords()

Return the number of records in the file

intgetRecordLength()

Get the length of the records in bytes.

voidreadHeader​(ByteBuffer in)

Read the header data from the DBF file.

voidreadHeader​(ReadableByteChannel channel)

Read the header data from the DBF file.

intremoveColumn​(String inFieldName)

Remove a column from this DbaseFileHeader.

voidsetNumRecords​(int inNumRecords)

Set the number of records in the file

StringtoString()

Get a simple representation of this header.

voidwriteHeader​(WritableByteChannel out)

Write the header data to the DBF file.

        在Heade对象中,要想获取字段的长度和精度,主要是使用下列两个方法,分别是:getFieldLength(index)和getFieldDecimalCount(index)。在IDE中运行以上程序后,可以在控制台中看到如下输出:

名称	254	C	0
大类	254	C	0
中类	254	C	0
小类	254	C	0
地址	254	C	0
省	254	C	0
市	254	C	0
区	254	C	0
WGS84_经	19	F	11
WGS84_纬	19	F	11

        可以看到,这里不仅输出了数据的长度,同时还输出了数据的精度。到此,使用第二种方式来进行dbf文件的解析已经成功实现。

三、总结

        以上就是本文的主要内容,本文则重点讲解在Java当中,如何使用GeoTools来进行属性表格的解析,首先在Qgis中重点介绍属性表格的字段信息,然后介绍一种常见的属性字段信息解析方式,其次介绍基于dbf的属性信息解析方法,直接解析dbf文件的方法在很多网络知识中很少见,但是却非常有用,比如在获取double类型的数值时,想获取精度值,就可以通过这种方式来获取。学习本文,不仅更加熟悉GeoTools对属性表格的解析,同时对于如何快速的将Shapefile的属性模型映射成空间库模型有更进一步的认识。行文仓促,难免有许多不足之处,如有不足还请各位专家博主在评论区留言指出,不胜感激。

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

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

相关文章

SQL优化 - 排序

文章目录 排序和索引降序索引 FilesortORDER BY 顺序问题ORDER BY LIMIT 排序和索引 如果ORDER BY操作使用了索引&#xff0c;那么就可以避免排序操作&#xff0c;因为索引本身就是按索引 key 排好序的。那什么情况下&#xff0c;ORDER BY会走索引呢&#xff1f; 例如&#…

专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结

目录 搜索 vs 深度优先遍历 vs 深度优先搜索 vs 宽度优先遍历 vs 宽度优先搜索 vs 暴搜 1.深度优先遍历 vs 深度优先搜索(dfs) 2.宽度优先遍历 vs 宽度优先搜索(bfs) 2.关系图暴力枚举一遍所有的情况 3.拓展搜索问题全排列 决策树 1. 计算布尔⼆叉树的值&#xff08;medi…

CF2013E Prefix GCD

【题目大意】 给定一个长度为 n n n 的数列 a 1 … n a_{1 \dots n} a1…n​&#xff0c;你可以将 a 1 … n a_{1 \dots n} a1…n​ 按照任意顺序进行重排&#xff0c;使得&#xff1a; ∑ i 1 n gcd ⁡ { a 1 , a 2 , a 3 , … , a n } \sum\limits_{i1}^{n}\gcd\left \{…

10.5学习

1.GateWay GateWay⽬标是取代Netflflix Zuul&#xff0c;它基于Spring5.0SpringBoot2.0WebFlux等技术开发&#xff0c;提供统⼀的路由⽅式&#xff08;反向代理&#xff09;并且基于 Filter(定义过滤器对请求过滤&#xff0c;完成⼀些功能) 链的⽅式提供了⽹关基本的功能&…

探索 Python 虚拟环境的奥秘:virtualenv 的魔法世界

文章目录 探索 Python 虚拟环境的奥秘&#xff1a;virtualenv 的魔法世界背景&#xff1a;为何选择 virtualenv&#xff1f;虚拟环境的守护者&#xff1a;virtualenv 是什么&#xff1f;安装 virtualenv&#xff1a;简单几步&#xff0c;开启隔离之旅掌握 virtualenv 的基本用法…

Relu激活

ReLU&#xff08;Rectified Linear Unit&#xff09;激活函数 是卷积神经网络&#xff08;CNN&#xff09;以及许多深度学习模型中最常用的激活函数之一。它的主要作用是引入非线性&#xff0c;使模型能够学习和表达更复杂的特征。以下是对ReLU激活函数的详细解释。 1. ReLU的…

C语言进阶版第16课—自定义类型:结构体

文章目录 1. 结构体类型的声明和初始化2. 结构体自引用3. 结构体内存对齐3.1 结构体内存对齐规则3.2 修改默认对齐数 4. 结构体传参4. 结构体实现位段5. 位段使用的注意事项 1. 结构体类型的声明和初始化 结构体在使用之前都要对其类型进行声明&#xff0c;关键字是struct&…

15分钟学 Python 第36天 :Python 爬虫入门(二)

Python 爬虫入门&#xff1a;环境准备 在进行Python爬虫的学习和实践之前&#xff0c;首先需要准备好合适的开发环境。本节将详细介绍Python环境的安装、必要库的配置、以及常用工具的使用&#xff0c;为后续的爬虫编写奠定坚实的基础。 1. 环境准备概述 1.1 为什么环境准备…

mp4转gif在线转换怎么转?7个视频转动图方法不容错过!(超简单)

mp4转gif在线转换怎么转&#xff1f;如今&#xff0c;将mp4视频转换为gif动图格式&#xff0c;满足了人们对易于分享和网络传播内容的需求。与mp4视频相比&#xff0c;gif动图文件体积相对较小&#xff0c;几乎所有网络平台都支持这种格式&#xff0c;无需额外安装插件或软件。…

CSID-GAN:基于生成对抗网络的定制风格室内平面设计框架论文阅读

CSID-GAN: A Customized Style Interior Floor Plan Design Framework Based on Generative Adversarial Network 摘要前言II. CSID-GAN METHODA. Overall FrameworkB. Algorithm and Loss Function III. DATASETS AND EVALUATION METRICSA. DatasetsB. Evaluation Metrics IV.…

信息安全工程师(34)访问控制模型

前言 访问控制模型是实现访问控制的基础&#xff0c;不同的访问控制模型提供了不同的访问控制策略和机制&#xff0c;以适应不同的安全需求。 一、自主访问控制模型&#xff08;DAC&#xff09; 定义&#xff1a;指资源的所有者有权决定谁可以访问其资源以及访问的方式。资源的…

如何实现事件流操作

文章目录 1 概念介绍2 使用方法3 示例代码我们在上一章回中介绍了通道相关的内容,本章回中将介绍StreamProvider组件.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 在Flutter中Stream是经常使用的组件,对该组件的监听可void main() {///让状态栏和程序的appBar融为一体…

macos安装mongodb

文章目录 说明安装和配置安装mongodb配置PATH变量 验证日志及数据存放目录 mac启动和关闭mongodb后台启动失败问题mongodb-compass(GUI) 说明 Homebrew core 列表目前已经将 MongoDB 移除,不再为其提供支持。但是使用国内镜像的brew还是可以安装的&#xff01;这里直接从官网下…

使用 Python 代码连接 PostgreSQL

Python 是一个功能非常强大的编程语言&#xff0c;尤其在与数据库交互时&#xff0c;提供了丰富的解决方案。在实际项目中&#xff0c;我们经常需要通过 Python 连接并操作数据库。为了简化这种操作&#xff0c;ORM&#xff08;对象关系映射&#xff09;框架提供了便利。ORM 能…

aws(学习笔记第一课) AWS CLI,创建ec2 server以及drawio进行aws画图

aws(学习笔记第一课) 使用AWS CLI 学习内容&#xff1a; 使用AWS CLI配置密钥对创建ec2 server使用drawio&#xff08;vscode插件&#xff09;进行AWS的画图 1. 使用AWS CLI 注册AWS账号 AWS是通用的云计算平台&#xff0c;可以提供ec2&#xff0c;vpc&#xff0c;SNS以及clo…

灵足时代:具身智能核心部件的新秀崛起——解析数千万元天使轮融资

在智能科技日新月异的今天,具身智能作为连接物理世界与数字世界的重要桥梁,正逐步成为科技创新的前沿阵地。近日,具身智能核心部件领域的新锐公司——“灵足时代”宣布完成数千万元天使轮融资,这一消息无疑为行业内外带来了强烈的震撼与期待。本轮融资由雅瑞智友科学家基金…

多用户网页聊天室(测试报告)

一、项目背景 随着现代互联网的快速发展&#xff0c;实时通信系统&#xff08;如聊天应用&#xff09;已成为人们日常交流的重要工具。多用户网页聊天室项目旨在为用户提供一个基于Web的实时聊天平台&#xff0c;支持用户之间的即时通信、好友管理和历史消息记录查看。为了提升…

ModuleNotFoundError: No module named ‘package‘

报错&#xff1a; Traceback (most recent call last): File “”, line 198, in run_module_as_main File “”, line 88, in run_code File "D:\python\helloworld.venv\Scripts\pip.exe_main.py", line 4, in File "D:\python\helloworld.venv\Lib\site-pac…

3分钟学会下载 blender

1. blender简介 Blender是一款开源的3D创作套件&#xff0c;它由Blender Foundation维护&#xff0c;并得到了全球志愿者和专业开发者的支持。Blender广泛应用于3D模型的制作、动画、渲染、视频编辑、游戏创建、模拟、 composting以及3D打印等多个领域。 功能特点&#xff1a…

Gitlab flow工作流

Gitlab flow Gitlab flow 是 Git flow 与 Github flow 的综合。它吸取了两者的优点&#xff0c;既有适应不同开发环境的弹性&#xff0c;又有单一主分支的简单和便利。它是 Gitlab.com 推荐的做法。 1 上游优先 Gitlab flow 的最大原则叫做"上游优先"&#xff08;…