作者 | Phodal
责编 | 伍杏玲
【程序人生 编者按】“中台之后,便是无代码编程。”无代码编程是什么?开发流程是怎么样的?有何优缺点?
无代码编程时代来了,就不需要程序员编写代码了吗?下面作者将跟大家聊聊无代码编程的那些事儿。
规模化的组织,经常要面临这样的挑战:每个应用的基础设施是相同的,部分的代码也是相同的,甚至于它们可能只是数据模型不同而已。结果却导致了,他/她们要一次又一次地重新编写一个应用。对于一个新的应用而言,它需要对接大量的三方(非自己团队)服务。服务之间的不断变化 ,导致了对应的使用方也需要发生变化。不断变化的业务,导致了前台的设计不断变化。为了应对快速谈的的前台服务,后台便诞生了中台,以提供快速的响应能力。而随着中台进一步沉淀,从某种形式上趋于稳定,而前台仍然需要快速地响应能力。
于是乎,作为一个前端开发人员,我们不断提炼和复用代码,想着的模式在之前的文章已提到了:
-
脚手架
-
组件库
-
模式库
-
模板和模板应用
对应的,我们还创建了一系列的 CLI、工具集、编程器插件以及设计系统,以完成整个系统的快速开发。然而,我们还缺少一套有效的工具,来统一化的管理这些工具。
换句话来说,就是:我们需要一个前端的中台,它便是无代码/低代码编程。
什么是无代码编程?
无代码/低代码是一种创建应用的方法,它可以让开发人员使用最少的编码知识,来快速开发应用程序。它可以在图形界面中,使用可视化建模的方式,来组装和配置应用程序。开发人员可以直接跳过所有的基础架构,只关注于使用代码来实现业务逻辑。
当然,从开发人员的角度来看,降低代码量,可能是:
-
框架本身处理了复杂性。毕竟 “复杂度同力一样不会消失,也不会凭空产生,它总是从一个物体转移到另一个物体或一种形式转为另一种形式。”
-
代码生成减少了工作量。大量的复制、粘贴需要更多的时间。
流程
只是凭借这个概念,我们是无法理解无代码编程的。于是,我画了一张图来展示相应的架构和流程:
低代码编程流
依照我的观点来看,我将无代码编程分为了两部分:
-
用于构建 UI 的编辑器——一种在线的拖拽式 UI 设计和页面构建工具
-
用于编写业务逻辑的流编辑器——通过流编程的方式来编写业务代码(多数是对于数据的处理)
UI 编程器
为了设计出我们的 UI 构建器,我们需要准备好一系列的基础设施:
-
UI 编程器。用于拖拽式设计 UI。
-
空白脚手架。一个带有完整的应用生命周期的项目,但是它是一个空白的项目——用于我们在构建 UI 的过程中,随时随地的添加组件和代码。
-
设计系统。我们需要一个完整的组件库,大量的页面模板,以及一定数量的模板应用,减少相应的开发工具量。
-
代码片段集。它将设计系统中的组件库进一步实例化成代码段,在完成编辑后通过 CLI 来动态编辑代码。
-
DSL(领域特定语言,可选)。中间生成物,用于隔离框架与设计。
流编程器
-
流编程器。用于拖拽式、输入编写业务代码。
-
后端服务。如果不能提供现成的后端服务,则需要拥有一个标准的 API 规范,以及相应的 mock server。
-
模式库。包含相应的业务处理代码,如通用的登录、数据获取、UI 交互等。
-
DSL(领域特定语言,可选)。同上
当然了,我们还需要能实时预览构建出来的应用。随后,我们执行了构建,而后构建出了一个半成品应用。开发人员只需要在它的基础上开发应用即可。而在开发人员开发的过程中,我们可以设计一系列的工具,来帮助开发人员更快速地构建应用。
-
编辑器插件。包含设计系统、模式库等的自动完成代码,以及组织内部常用的代码库。
-
调试工具。对于混合类型的应用而言,我们还需要一个开发工具来快速构建应用。
从上述的流程上来看,无代码编程还具有以下的特点:
-
拖放式界面。又或者是可视化模型——基于节点和箭头。
-
基于视觉的设计。
-
可扩展的设计。如对于插件、插件商店,社区等一系列的支持。
-
跨平台功能。支持 PC Web 应用开发,支持移动应用构架等。
-
强大的部署后。即平台包含着整个应用的生命周期。
-
拥有丰富的集成支持。可以随意的找到需要的组件,以及对应的后台服务。
-
配置化。它也意味着大量的自定义配置。
-
自制的领域特定语言(可选)。用于构建时优化。
优缺点
相应的,它具有以下的一些优点:
-
高效。不用多说,节省时间和开发成本。
-
有限的 Bug,安全性。
-
低成本。其所需的预算非常有限。
-
易用(取决于设计)。
-
开发速度更快。
-
开发过程中的 AI 。
-
维护成本低。
对应的相应的缺点有:
-
仍然需要编程技能。
-
受限的自定义能力。
-
可扩展性成了新的问题。
-
集成受限。
就当前而言,低代码开发平台通常分为两大类:
-
对于外部:制作简单的产品,如网络移动应用程序
-
对于内部:为您的团队或企业创建业务应用程序
诸如只使用 CRUD、表单、验证、简单聚合、分页等简易的服务。最常见的例子就是表单构建了,诸如金数据这样的应用,便是可以直接通过拖拽元素来生成,相应的开源实现有 jQuery Form Builder。
对于开发人员来说,我们只需要定义好数据模型,再通过拖拽来决定元素的位置即可。从这种角度来看,只要能使用 Serverless 构建的应用和服务,都可以直接使用低代码开发模式。
开发流程对比
从我们的理解来看,传统应用的开发流程是:
-
分析、设计、确认、规划需求。
-
设计系统架构。
-
搭建前后端项目。选择技术栈、从零开始搭建或者从脚手架中创建。
-
搭建持续集成。
-
创建线框图和高保真原型。
-
设计数据模型,定义前后端契约,即 API URI、方法、字段等。
-
前后端实现业务逻辑。
-
前端实现 UI 页面。
-
集成第三方后端服务。
-
功能需求测试(DEV、QA、ST、UAT)
-
跨功能需求测试(安全性、性能等)
-
部署到生产环境。
低代码开发流程:
-
分析、设计、确认、规划需求
-
选择需要的第三方 API
-
在可视 IDE 中绘制应用程序的工作流程、数据模型和用户界面。
-
连接 API——通常使用服务、函数发现。
-
编写业务逻辑(可选)。手动代码添加到前端或者自定义自动生成的 SQL 查询。
-
用户验收测试。
-
部署到生产环境。
从步骤上来看,无代码编程少了几个步骤。这些步骤都因为大量丰富的内部系统集成,而变得非常简单。
适用场景
就当前而言,无代码编程实际上是一种高度的场景预设的模式。因此,它存在一定的适用场景:
-
模型驱动开发。
-
快速 UI 构建。
-
极简的业务功能。
-
IT 资源受限。在资源受限的情况下,能快速开发出符合业务需求的应用最重要。
而从流程上来看,对于一部分的应用来说,我们并不能实现无代码编程——存在一些业务上的不同之处。因此,多数场景之下,只是实现了低代码编程。
若是想真实的无代码编程,则需要一些更特定的场景:
-
设计表格(输入数据)
-
创建报告(组织数据)
-
常规调度和自动化过程(操纵数据)
更多的场景正在探索中。
无代码编程的挑战
无代码编程,除了需要准备好上述的一系列基础设施,还不可避免地会遇到一系列挑战。
-
谁来写这部分代码?
-
客户端的基础设施准备。
-
服务端的服务平台搭建。
-
统一用户体验设计。设计出一系列能便利组合的组件,及对应的模板页面。与此同时,它们还能适应于不同的风格,即有多样性的主题支持。
-
DevOps 流水线设计。低代码编程,依赖于一系列的自动化工具,以实现构建、调试、部署以及维护,同时还包含应用的测试。
-
领域语言设计。
-
自动化测试。如果我们的前端代码是自动生成的,那么我们还需要对它们进行测试吗?这是一个好问题,而如果代码是自动生成的,那么测试也应该是自动生成的。毕竟要在平台上,编写大量的自动化测试,以保证平台的质量。
其中,有一些部分略微复杂一些,我们大概可以探索一下。
谁来写这部分代码?
在我们创建这样一个平台和工具时,我们要考虑的第一个问题是,这个工具是为谁写的?
-
没有编程经验的人。如业务人员,他/她们对于业务系统有着非常丰富的经验。
-
有编程知识,但是没有经验的人。
-
有一定经验的开发人员。
-
有丰富经验的开发人员。对于专业的人来说,自动化就意味着缺少灵活度。甚至与自己编写相比,他们要花费更多的时间来修复生成的代码。
显然,对于相当有经验的开发人员而言,这个工具并不一定是他们所需要的。
客户端基础设施
从我的理解来看,它适合于快速的 MVP 构建,并且生成的代码还应该方便修改,而不是诸如早期的 DreamWeaver 或者 FrontPage 这样的工具。
而与此同时,由于面向的开发人员水平不同,我们所需要做的工具也不同:
-
支持云构建和调试。
-
GUI 编程应用。
-
代码生成。
-
设计系统体系构建。组件库搭建,模板应用创建等。
更难的是,容易让开发人员能接受代码生成。
服务端
对于一个低代码平台而言,它对应的后端应该:
-
大量可用的现有服务。身份验证、安全性、推送能力、地图等等。
-
快速构建出后端服务。若是有内部 Serverless 或者 FaaS 方案,可以说是再好不过了。
-
方便与第三方服务集成。
-
灵活性。支持多语言等。
统一的后端服务 API,对于后端服务来说,我们需要一个通用的范式。所有的 API 应该按照这样的范式来设计。不过,作为一个 API 的消费方,我们可能没有这么大的权力,但是我们可以采用装饰器模式,即封装第三方 API 成统一的方式。为此,我们采用的方式,仍然是:
-
契约。诸如 Swagger UI,它可以直接创建一个简易可用的服务。
-
BFF。即我们一一去按我们的需要,去封装这些第三方应用。
-
查询语言。与自己编写 BFF 相比,更简单的方式是采用:GraphQL 这样的后端查询语言,便捷性更高、模式更加灵活。
在开发前的设计期里,我们需要首先设计出对应的领域模型。
领域语言设计
低代码环境使用(图形)建模语言来指定整个系统、产品的行为。它意味着:
-
将数据结构、领域模式应用到程序的各个层级中。
-
将业务规则、决策融入到应用中(层级)。
这也就意味着,我们需要设计一个模型语言。而它对于我们而言,实际上是领域特定语言(DSL)。如下是一个简单的 DSL 示例,用于描述使用到的组件:
{'style': '','id': 2,'blocks':
[{'content': {'content': 'content','title': 'hello'},'type':'card'}]
}
除此,我们还需要设计对应的布局 DSL,诸如于:
H:[circle1(circle1.height)] // set aspect-ratio for circle1
HV:[circle2..5(circle1)] // use same width/height for other circles
H:|[circle1]-[circle2]-[circle3]-[circle4]-[circle5]|
V:|~[circle1..5]~| // center all circles vertically
最后,我们还需要将流代码,转换为真实的项目代码。三种类型的 DSL 结合下来,都不是一个轻松的工具。
原型设计
写好现有的组件,通用型接口。如常见的登录接口,对于使用登录接口的业务来说,它们只关心三部分的内容:
-
成功登录。
-
取消登录。
-
登录失败。对于客户端而言,可以视为取消登录。对于服务端来说,则可能是密码错误、用户名不存在、账号被锁定等。
对应于以上情景,又有一些通用的逻辑处理:
-
登录成功。保存 Token,并返回历史页面。
-
登录失败。弹出一个自定义内容的提示框。
这些代码是相似的。
前端原型
在一些简单的前端应用里:
-
模板。只是在使用这些模板,再为这些模板设置相应的属性,绑定对应的值。
-
数据。其过程都只是在各种保存变量的值,并 CRUD 这些变量的路上。为此,我们需要一个数据处理的管道架构设计,用于处理这些值。
-
函数。事实上,我们的所有函数都只是一些管理函数,只用于处理这些对应的逻辑。
这些常见的功能都可以做成一些组件,它们对于某些应用来说,代码相应的重复。
-
无限加载页面。只需要绑定上 API,再控制一下元素的显示与隐藏即可。
-
表单。定义好字段即类型,对应的前后台逻辑都有了。除此,我们还需要为它们自定义好常见的规则,如正则表达式。而一旦表单之间有联动,那么这个组件的设计就更加麻烦了。
-
卡片式元素。
-
表单和表格展示。
-
常见图表。事实上,已经有一系列的图表工具了,我们只是在它们在基础上,进行了二次封装而已——使得它们可以变成领域语言的形式。
-
高级的响应式布局。与每个应用独立开发布局不同的是,低代码平台需要提供一套强大的自定义、响应式布局设计——即要满足移动端的通用模式,还要满足桌面版的通用模式。如对于大量数据来说,桌面端使用的是 Pagination,移动端使用的是无限滚动。
后端原型
事实上,对于后端来说,低成本平台意味着,代码生成及服务生成。而服务本身是有限的,既然是业务上发生了一些变化,后端服务也可能并不会发生变化。
它也意味着:
-
微服务化。每个后端服务要尽可能的小。
-
API 规范化。即采用统一的 API 格式,接受统一的权限管理
-
大量的 API 服务。
-
快速集成第三方服务方案。集成第三方服务是开发应用不可避免的情况。为了应对这个问题,我们需要做准备好对应的创建服务逻辑,传入第三方服务所需要的参数,便可以直接生成我们的转发服务。
那么,问题来了,既然如此,我们是否能提供一个定制的工具呢?让每个人可以创建自己的组件流?
答案,显然是可以的。
概念证明
于是乎,在我最近设计的 PoC (概念证明)里,采用的是 Anguar 框架。相应的基本思想如下:
-
使用 Material Design 作为组件库,使用 CDK 的 Drag 来实现拖拽功能。每个拖拽的组件,称为 Element(元素),由 ElementDispatcher 由根据数据生成对应的组件。可拖拽的部分由两部分组成:布局 + 元素。
-
UI 构建完后,生成对应的 DSL,目前采用的是 JSON。毕竟数据结构是最简单的领域特定语言。
-
借由 Angular Schematics 解析这部分的 DSL,来生成相应的项目代码。
相关开源项目:
拖拽式 Web 建造工具:https://grapesjs.com/
基于 Flow 的编程工具:https://noflojs.org/
DSL 布局生成器:https://github.com/ijzerenhein/autolayout.js/
可视化数据流编辑器:https://github.com/Gregwar/blocks.js
基于 React 的内容生成器:https://github.com/vigetlabs/colonel-kurtz
参考资料:
https://www.process.st/low-code/
https://medium.com/@stefan.dreverman/decisions-to-take-for-your-low-code-architecture-c0768b606f
https://medium.com/@stefan.dreverman/analyzing-coinmarketcap-data-with-neo4j-4930ba0068e1
https://www.outsystems.com/blog/what-is-low-code.html
https://medium.com/@stefan.dreverman/starting-a-low-code-application-architecture-13170fcd6fc7
https://www.quora.com/What-is-low-code
作者简介:黄峰达(Phodal),ThoughtWorks Senior Consultant,CSDN 博客专家。长期活跃于 GitHub、CSDN,专注于物联网和前端领域。出版著作《自己动手设计物联网》,以及《Growth:全栈增长工程师指南》等六本电子书,并译有《物联网实战指南》。
本文经授权转自作者公众号「Phodal」。
学了两年Python没抓住重点只学了皮毛,到底错在哪?
热 文 推 荐
☞ 程序员找工作黑名单:除了 996.ICU,程序员还将如何自救?
☞ 30 位 90 后霸榜:名企 CEO、10 亿身家,马云的接班人?
☞ 何恺明的GN之后,权重标准化新方法能超越GN、BN吗? | 技术头条
☞ 2019年关于VM和Kubernetes的思考 | 技术头条
System.out.println("点个在看吧!");
console.log("点个在看吧!");
print("点个在看吧!");
printf("点个在看吧!\n");
cout << "点个在看吧!" << endl;
Console.WriteLine("点个在看吧!");
Response.Write("点个在看吧!");
alert("点个在看吧!")
echo "点个在看吧!"
点击阅读原文,输入关键词,即可搜索您想要的 CSDN 文章。
你点的每个“在看”,我都认真当成了喜欢



目录
前言
今天是六.一儿童节,在这里祝各位大朋友们儿童节快乐,同时祝祖国的所有花朵们更加快乐。童年的欢乐可以治愈一切。不知道各位的儿童节是怎么度过的呀,是在陪孩子呢,还在在享受自己的欢乐时光,只愿大家都欢乐就好。这是写在最前面祝福的话,愿我们都开心快乐。
最近碰到有朋友咨询,大致的问题是,他在项目中要实现KMZ数据的解析和WebGIS的可视化。刚好他用的技术栈是Java,同时KMZ的解析在各个网站上的相关解析代码不多。有很多代码是解析KML的,但是解析KMZ的相对比较少。一时没有参考的例子,希望能结合JAVA讲一下如何进行KMZ数据的解析。其实话说回来,虽然大致了解KMZ是什么数据,但是在之前的项目过程中,接触的空间数据也基本都是shp、gdb等等,至于google的KMZ还真的是第一回接触。
本文主要讲解如何用JAVA语言,直接解析KMZ数据。文章首先介绍google地图中的KMZ和KML数据,然后使用代码的方式实现数据的解析,最后展示解析成果以及如何将数据转换成空间WKT数据。关于JAVA解析KML的博客和资料有不少,但是KMZ文件的还是比较稀少的,供各位朋友在工作中解析KMZ文件有一个参考。
一、关于KMZ和KML
在进行相关文件的解析之前,首先我们来看一下KMZ和KML这两种文件,先了解这两种文件是什么?用来做什么的,具体的文件内容是什么样的。本节主要提供这些基础知识的讲解。
1、KMZ是什么
KMZ 文件包含主 KML 文件以及0个或多个用 ZIP 格式打包成一个单元的支持文件(称为归档)。然后,KMZ 文件就可以作为单个实体进行存储和通过电子邮件发送。NetworkLink 可从网络服务器提取 KMZ 文件。将 KMZ 文件解压缩后,主 .kml 文件及其支持文件便分离成其各自的原始格式和目录结构,以及原始文件名和扩展名。除了变成归档格式外,ZIP 格式也会受到压缩,因此归档只能包含一个大型 KML 文件。根据 KML 文件的内容,此过程通常会产生10:1的压缩。10千字节的 KML 文件可以用1千字节的 KMZ 文件来提供。
KMZ是Google Earth默认的输出文件格式,是一个经过ZIP格式压缩过的KML文件,当我们从网站上下载KMZ文件的时候,Windows会把KMZ文件认成ZIP文件,所以另存的时候文件后缀会被改成.ZIP,因此需要手动将文件后缀改成.KMZ。 KMZ文件用ZIP工具软件打开,然后解压缩即可得到原始KML文件。当然,KMZ文件也有自己的好处,就是KMZ文件的自身可以包含影像,这样就可以不依赖引用网络上的截图。 一般情况下,双击KMZ/KML文件即可从Google Earth中打开地标文件,但是需要注意的是,KMZ/KML地标文件名不能包含中文字符,文件存放的路径也不能有中文字符,否则将无法在Google Earth中打开。
这里我们以漂亮国的全球基地为说明,验证一下上述的内容。把KMZ文件的后缀名修改为zip,然后用压缩文件打开。可以看到以下的文件:
总结一下,KMZ就是把KML文件,进行了一个打包。这个很重要,在后面的解析过程中,会用到这个知识点。讲完了KMZ,下面介绍一下KML。
2、KML是什么
KML 代表 钥匙孔标记语言。此 GIS 格式基于 XML,主要用于 Google 地球。KML由Keyhole Inc开发,后来被Google收购.KMZ(KML-Zipped)取代KML成为默认的Google地球地理空间格式,因为它是文件的压缩版本。KML/KMZ于2008年成为开放地理空间联盟的国际标准。经度和纬度分量(十进制度)由 1984 年世界大地测量系统 (WGS84) 定义。垂直分量(高度)以米为单位从 WGS84 EGM96 大地水准面垂直基准面开始测量。
KML (keyhole markup language)是以XML语言为基础开发的一种文件格式,用来描述和存储地理信息数据(点、线、面、图片等),是纯粹的xml文本格式,可用记事本打开编辑,所以kml文件很小。KML跟XML文件最大的不同就是KML描述的是地理信息数据。最早开发KML的是keyhole公司,2004年Goole收购keyhole并用KML开发GooleEarth。KML是原先的Keyhole客户端进行读写的文件格式,是一种XML描述语言,并且是文本格式,这种格式的文件对于Google Earth程序设计来说有极大的好处,程序员可以通过简单的几行代码读取出地标文件的内部信息,并且还可以通过程序自动生成KML文件,因此,使用KML格式的地标文件非常利于Google Earth应用程序的开发。
这里我们还是以上面的kml文件为说明,将打开的示例xml文件内容展示如下:
- "1.0" encoding="UTF-8"?>
- <kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2">
- <Document>
-
- <Folder>
- <name>Point Featuresname>
- <description>Point Featuresdescription>
- <Placemark>
- <description>Airportdescription>
- <name>name>
- <Point>
- <coordinates>-80.0408900000,32.8985600000,0coordinates>
- Point>
- Placemark>
- <Placemark>
- <description>Airportdescription>
- <name>name>
- <Point>
- <coordinates>-110.8822600000,32.1652200000,0coordinates>
- Point>
- Placemark>
- Folder>
- Document>
- kml>
以上就是一个KML文件的示例,其主体内容就是一个XML。它以XML为主体,用来存储地理空间数据。因此对KML数据的解析,其本质就是对XML文件的解析。
二、Java解析实例
本节主要以代码实战的方式介绍使用Java编程语言实现对KML语言和KMZ语言的解析。由于涉及到xml的解析,这里不采用最原始的dom解析方式。对于KML语言,有成熟的组件de.micromata.jak.JavaAPIforKml对KML的解析。这里对相关的解析组件进行介绍:
序号 | 组件名称 | 作用 |
1 | de.micromata.jak.JavaAPIforKml | KML文件解析 |
2 | org.apache.commons.commons-compress | 压缩包解压 |
3 | com.vividsolutions.jts | JTS wkt字符串构建 |
1、POM.xml引用
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0modelVersion>
- <groupId>com.yelanggroupId>
- <artifactId>gdal_demo1artifactId>
- <version>0.0.1-SNAPSHOTversion>
- <name>gdal_demo1name>
- <description>试验description>
- <properties>
- <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
- <maven.compiler.source>1.8maven.compiler.source>
- <maven.compiler.target>1.8maven.compiler.target>
- properties>
- <dependencies>
-
- <dependency>
- <groupId>de.micromata.jakgroupId>
- <artifactId>JavaAPIforKmlartifactId>
- <version>2.2.1version>
- dependency>
-
- <dependency>
- <groupId>org.apache.commonsgroupId>
- <artifactId>commons-compressartifactId>
- <version>1.21version>
- dependency>
- <dependency>
- <groupId>com.vividsolutionsgroupId>
- <artifactId>jtsartifactId>
- <version>1.13version>
- dependency>
- dependencies>
- project>
2、KML 基类定义
这里进行kml 基类定义,将name、description、List
- package com.yelang.kmzcase;
- import java.util.List;
- import de.micromata.opengis.kml.v_2_2_0.Coordinate;
- /**
- * kml 基类,将name、description、List
进行统一封装 - * @author 夜郎king
- */
- public class KmlBaseEntity {
- private List
points; - private String name;
- private String description;
- public List
getPoints() { - return points;
- }
- public void setPoints(List
points) { - this.points = points;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getDescription() {
- return description;
- }
- public void setDescription(String description) {
- this.description = description;
- }
- public KmlBaseEntity(List
points, String name, String description) { - super();
- this.points = points;
- this.name = name;
- this.description = description;
- }
- public KmlBaseEntity() {
- super();
- }
- }
3、空间对象的定义
空间对象常见的类型包括点(Point)、线(Polyline)、面(Polygon)三种类型。这里我们将根据需要定义不同的空间对象。下面分别给出实例代码:
KmlPoint.java
- package com.yelang.kmzcase;
- import java.util.List;
- import de.micromata.opengis.kml.v_2_2_0.Coordinate;
- public class KmlPoint extends KmlBaseEntity{
- private String color;
- public String getColor() {
- return color;
- }
- public void setColor(String color) {
- this.color = color;
- }
- public KmlPoint(List
points,String name,String description,String color) { - super(points, name, description);
- this.color = color;
- }
- public KmlPoint() {
- super();
- }
- }
KmlLine.java
- package com.yelang.kmzcase;
- public class KmlLine extends KmlBaseEntity {
- private String color;
- private long width;
- public String getColor() {
- return color;
- }
- public void setColor(String color) {
- this.color = color;
- }
- public long getWidth() {
- return width;
- }
- public void setWidth(long width) {
- this.width = width;
- }
- }
KmlPolygon.java
- package com.yelang.kmzcase;
- /**
- * @program: 面状实体
- **/
- public class KmlPolygon extends KmlBaseEntity {
- private String color;
- public String getColor() {
- return color;
- }
- public void setColor(String color) {
- this.color = color;
- }
- }
4、Kml解析工具类
这里定义Kml的解析工具类,主要负责解析KML,然后根据不同的图层,将属性和空间坐标点信息赋值给不同的空间数据集合。网上有一些解析的代码,仅解析name属性。这里扩展了其它的属性,包括描述属性。详细代码如下:
- package com.yelang.kmzcase;
-
- import de.micromata.opengis.kml.v_2_2_0.*;
- import java.io.File;
- import java.io.InputStream;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * @description: KML文件解析
- **/
- public class ParsingKmlUtil {
- /**
- * 解析kml文件
- */
- public KmlData parseKmlByFile(File file) {
- Kml kml = Kml.unmarshal(file);
- return getByKml(kml);
- }
- /**
- * 解析kml文件流
- *
- * @param inputstream
- * @return
- */
- public KmlData parseKmlByInputstream(InputStream inputstream) {
- Kml kml = Kml.unmarshal(inputstream);
- return getByKml(kml);
- }
- /**
- * Kml对象转自定义存储对象
- *
- * @param kml
- * @return
- */
- public KmlData getByKml(Kml kml) {
- KmlData kmlData = new KmlData();
- kmlData.setKmlPoints(new ArrayList<>());
- kmlData.setKmlLines(new ArrayList<>());
- kmlData.setKmlPolygons(new ArrayList<>());
- Feature feature = kml.getFeature();
- parseFeature(feature, kmlData);
- return kmlData;
- }
-
- /**
- * 解析kml节点
- * @param feature
- * @param kmlData
- */
- private void parseFeature(Feature feature, KmlData kmlData) {
- if (feature != null) {
- if (feature instanceof Document) {
- List
featureList = ((Document) feature).getFeature(); - featureList.forEach(documentFeature -> {
- if (documentFeature instanceof Placemark) {
- getPlaceMark((Placemark) documentFeature, kmlData);
- } else {
- parseFeature(documentFeature, kmlData);
- }
- });
- } else if (feature instanceof Folder) {
- List
featureList = ((Folder) feature).getFeature(); - featureList.forEach(documentFeature -> {
- if (documentFeature instanceof Placemark) {
- getPlaceMark((Placemark) documentFeature, kmlData);
- } else {
- parseFeature(documentFeature, kmlData);
- }
- });
- }
- }
- }
-
- private void getPlaceMark(Placemark placemark, KmlData kmlData) {
- Geometry geometry = placemark.getGeometry();
- /*String name = placemark.getName();
- placemark.getDescription();
- System.out.println(placemark.getDescription());
- if (name == null) {
- name = placemark.getDescription();
- }
- parseGeometry(name, geometry, kmlData);*/
- parseGeometry(placemark,geometry,kmlData);
- }
- /**
- * 解析点线面形状的数据分别放入存储对象
- * @param placemark placemark对象
- * @param geometry 形状类型
- * @param kmlData 存储对象
- */
- private void parseGeometry(Placemark placemark, Geometry geometry, KmlData kmlData) {
- if (geometry != null) {
- if (geometry instanceof Polygon) {
- Polygon polygon = (Polygon) geometry;
- Boundary outerBoundaryIs = polygon.getOuterBoundaryIs();
- if (outerBoundaryIs != null) {
- LinearRing linearRing = outerBoundaryIs.getLinearRing();
- if (linearRing != null) {
- List
coordinates = linearRing.getCoordinates(); - if (coordinates != null) {
- outerBoundaryIs = ((Polygon) geometry).getOuterBoundaryIs();
- addPolygonToList(kmlData.getKmlPolygons(), placemark, outerBoundaryIs);
- }
- }
- }
- } else if (geometry instanceof LineString) {
- LineString lineString = (LineString) geometry;
- List
coordinates = lineString.getCoordinates(); - if (coordinates != null) {
- coordinates = ((LineString) geometry).getCoordinates();
- addLineStringToList(kmlData.getKmlLines(), coordinates, placemark);
- }
- } else if (geometry instanceof Point) {
- Point point = (Point) geometry;
- List
coordinates = point.getCoordinates(); - if (coordinates != null) {
- coordinates = ((Point) geometry).getCoordinates();
- addPointToList(kmlData.getKmlPoints(), coordinates, placemark);
- }
- } else if (geometry instanceof MultiGeometry) {
- List
geometries = ((MultiGeometry) geometry).getGeometry(); - for (Geometry geometryToMult : geometries) {
- Boundary outerBoundaryIs;
- List
coordinates; - if (geometryToMult instanceof Point) {
- coordinates = ((Point) geometryToMult).getCoordinates();
- addPointToList(kmlData.getKmlPoints(), coordinates, placemark);
- } else if (geometryToMult instanceof LineString) {
- coordinates = ((LineString) geometryToMult).getCoordinates();
- addLineStringToList(kmlData.getKmlLines(), coordinates, placemark);
- } else if (geometryToMult instanceof Polygon) {
- outerBoundaryIs = ((Polygon) geometryToMult).getOuterBoundaryIs();
- addPolygonToList(kmlData.getKmlPolygons(), placemark, outerBoundaryIs);
- }
- }
- }
- }
- }
-
- /**
- * 保存面状数据
- *
- * @param kmlPolygonList 已有面状数据
- * @param placemark placemark对象
- * @param outerBoundaryIs 面状信息
- */
- private void addPolygonToList(List
kmlPolygonList, Placemark placemark, Boundary outerBoundaryIs) { - LinearRing linearRing = outerBoundaryIs.getLinearRing();// 面
- KmlPolygon kmlPolygon = new KmlPolygon();
- kmlPolygon.setPoints(linearRing.getCoordinates());
- kmlPolygon.setName(placemark.getName());
- kmlPolygon.setDescription(placemark.getDescription());
- kmlPolygonList.add(kmlPolygon);
- }
- /**
- * 保存线状数据
- *
- * @param kmlLineList 已有线状数据
- * @param coordinates 线状经纬度数据
- * @param name 线状名称
- */
- private void addLineStringToList(List
kmlLineList, List coordinates, Placemark placemark) { - KmlLine kmlLine = new KmlLine();
- kmlLine.setPoints(coordinates);
- kmlLine.setName(placemark.getName());
- kmlLine.setDescription(placemark.getDescription());
- kmlLineList.add(kmlLine);
- }
- /**
- * 保存点状数据
- *
- * @param kmlPointList 已有点状数据
- * @param coordinates 点状经纬度数据
- * @param name 点状名称
- */
- private void addPointToList(List
kmlPointList, List coordinates, Placemark placemark) { - KmlPoint kmlPoint = new KmlPoint();
- kmlPoint.setName(placemark.getName());
- kmlPoint.setDescription(placemark.getDescription());
- kmlPoint.setPoints(coordinates);
- kmlPointList.add(kmlPoint);
- }
-
- }
在定义了上述的代码之后,基本就可以实现了纯Java对KML文件的解析。下一节将调用上面的代码进行相应文件的解析。
三、KML文件的解析
本节将重点介绍如何解析KML文件。
1、KML解析测试
- package com.yelang.kmzcase;
- import org.apache.commons.compress.archivers.ArchiveEntry;
- import org.apache.commons.compress.archivers.ArchiveInputStream;
- import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
- import com.vividsolutions.jts.geom.GeometryFactory;
- import com.vividsolutions.jts.io.WKTWriter;
- import de.micromata.opengis.kml.v_2_2_0.Coordinate;
- import de.micromata.opengis.kml.v_2_2_0.Kml;
- import java.io.*;
- import com.vividsolutions.jts.geom.*;
- public class KMZParser {
- public static void parseKml() throws IOException {
- ParsingKmlUtil parsingKmlUtil = new ParsingKmlUtil();
- File file = new File("C:/BaiduDownload/美军基地-地图数据(kmz)/美空军基地 - 副本/US-AFB.KML"); // 文件地址自己修改
- KmlData kmlData = parsingKmlUtil.parseKmlByFile(file);
- // assert kmlData != null;
- if (kmlData.getKmlPoints().size() > 0) {
- for (KmlPoint k : kmlData.getKmlPoints()) {
- GeometryFactory geoFactory = new GeometryFactory();
- Coordinate coord = k.getPoints().get(0);
- com.vividsolutions.jts.geom.Coordinate jtCoord = new com.vividsolutions.jts.geom.Coordinate(coord.getLongitude(), coord.getLatitude(), coord.getAltitude());
- // 使用GeometryFactory创建一个点
- Geometry point = geoFactory.createPoint(jtCoord);
- WKTWriter writer = new WKTWriter();
- String wkt = writer.write(point);
- System.out.println(k.getPoints() + "\t"+ wkt +"\t"+ k.getDescription() + "\t " + k.getName());
- }
- }
- }
- // 使用示例
- public static void main(String[] args) throws IOException {
- KMZParser.parseKml();
- }
- }
由于在XML中的坐标是一个数组,如果想把这些数据保存到空间数据库中,需要进行格式转换,比如从WKT字符串转为Geometry。当然,保存到空间数据库中,有很多种方法,这里我们介绍一种,基于JTS的方式构建WKT字符串,因为在MyBatis-Plus中,可以直接操作WKT字符串。将坐标转换WKT字符串的方法如下:
- com.vividsolutions.jts.geom.Coordinate jtCoord = new com.vividsolutions.jts.geom.Coordinate(coord.getLongitude(), coord.getLatitude(), coord.getAltitude());
- // 使用GeometryFactory创建一个点
- Geometry point = geoFactory.createPoint(jtCoord);
- WKTWriter writer = new WKTWriter();
- String wkt = writer.write(point);
在控制台中执行以上方法可以看到以下信息的输出,说明KML文件解析成功。
- [-80.04089,32.89856] POINT (-80.04089 32.89856) Airport CHARLESTON AFB/INTL 查尔斯顿空军基地/国际机场
- [-110.88226,32.16522] POINT (-110.88226 32.16522) Airport DAVIS-MONTHAN AFB 戴维斯-蒙森空军基地
- [-110.34393,31.58844] POINT (-110.34393 31.58844) Airport LIBBY AAF/SIERRA VISTA MUN 利比空军基地/谢拉维斯塔
- [-98.49243,33.98621] POINT (-98.49243 33.98621) Airport SHEPPARD AFB/WICHITA FALLS MUN 谢泼德空军基地/威奇托福尔斯??
- [-72.52899,42.19849] POINT (-72.52899 42.19849) Airport WESTOVER ARB/METROPOLITAN 韦斯特欧弗空军基地
- [-84.04541,39.82544] POINT (-84.04541 39.82544) Airport WRIGHT-PATTERSON AFB 赖特-帕特森空军基地
- [-84.07013,39.80072] POINT (-84.07013 39.80072) Airport WRIGHT-PATTERSON AFB 赖特-帕特森空军基地
2、KMZ解析测试
网上很多博客都只讲了如何解析KML,但是对于KMZ的解析介绍比较少,以JAVA为开发语言解析更少了。上面的方法也只实现了KML的解析,如果把文件换成KMZ,肯定会报错的。不信来试试。错误信息如下:
如果发生了以上的异常,不要急。发生这个异常的原因其实在文章的开头就讲过了。如果看到这里前面没有印象的,可以翻到前面去看一下。还是简单说明一下吧,主要是KMZ是KML的压缩包,而以上代码是KML的解析,没有对KMZ进行解压。这里有两种方法来实现,第一种是将KMZ文件进行解压,然后对解压后的文件解析,肯定没问题。第二种是在压缩包中读取,然后对压缩流信息进行解析。第一种方式会增加不必要的脏文件,第二种则不会,因为原始文件只有一个。 下面我们采用第二种实现方式,首先来定义一个处理接口(必须要):
- package com.yelang.kmzcase;
- import java.io.IOException;
- import java.io.InputStream;
- /**
- * kml转换类,用于实现kml的自定义识别与读取
- * @author 夜郎king
- */
- public interface IKMLParser {
- /**
- * @param kmlInputStream
- * @throws IOException
- */
- void parseKML(InputStream kmlInputStream) throws IOException;
- }
然后再定义针对KMZ的内容解析代码(基于在线解压的方式),然后已输入流的方式完成内容解析,这个代码网上比较少,如果需要KMZ文件解析,可以作为参考:
- public static void parseKMZFile(File kmzFile, IKMLParser kmlParser) throws IOException {
- try (InputStream fileInputStream = new FileInputStream(kmzFile);
- ArchiveInputStream archiveInputStream = new ZipArchiveInputStream(fileInputStream)) {
-
- ArchiveEntry entry;
- while ((entry = archiveInputStream.getNextEntry()) != null) {
- String name = entry.getName();
- if (name.toLowerCase().endsWith(".kml") || name.toLowerCase().endsWith(".kmz")) {
- // 如果发现.kml或.kmz文件,可以将其内容读取出来并传递给KMLParser处理
- kmlParser.parseKML(archiveInputStream);
- }
- }
- }
- }
-
- public static void parseKmz() throws IOException {
- File kmzFile = new File("C:/BaiduDownload/美军基地-地图数据(kmz)/美国全球基地.kmz");
- KMZParser.parseKMZFile(kmzFile, new IKMLParser() {
- @Override
- public void parseKML(InputStream kmlInputStream) throws IOException {
- // 在这里实现你的KML解析逻辑
- // 例如,可以将读取的KML内容转换为字符串
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(kmlInputStream))) {
- String line;
- StringBuffer xmlContent = new StringBuffer(1024);
- while ((line = reader.readLine()) != null) {
- // 处理每一行KML数据
- xmlContent.append(line);
- }
- // System.out.println(xmlContent);
- Kml kml = Kml.unmarshal(xmlContent.toString());
- ParsingKmlUtil pku = new ParsingKmlUtil();
- KmlData kmlData = pku.getByKml(kml);
- if (null != kmlData.getKmlPoints()&&kmlData.getKmlPoints().size() > 0) {
- for (KmlPoint kp : kmlData.getKmlPoints()) {
- GeometryFactory geoFactory = new GeometryFactory();
- Coordinate coord = kp.getPoints().get(0);
- com.vividsolutions.jts.geom.Coordinate jtCoord = new com.vividsolutions.jts.geom.Coordinate(coord.getLongitude(), coord.getLatitude(), coord.getAltitude());
- // 使用GeometryFactory创建一个点
- Geometry point = geoFactory.createPoint(jtCoord);
- WKTWriter writer = new WKTWriter();
- String wkt = writer.write(point);
- System.out.println(kp.getPoints() + "\t"+ wkt +"\t"+ kp.getDescription() + "\t " + kp.getName());
- }
- System.out.println("美军全球基地的数据是==>" + kmlData.getKmlPoints().size());
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- });
- }
上述代码的主要逻辑是,使用compress对KMZ文件进行在线解压,然后动态拼接KML内容,最后解析KML文件,然后提取空间信息。经过上述步骤,完成信息的解析。在运行以上的代码,发现KMZ文件已经成功解析。
至此已经完成KMZ和KML文件的解析。
四、总结
以上就是本文的主要内容,本文主要讲解如何用JAVA语言,直接解析KMZ数据。文章首先介绍google地图中的KMZ和KML数据,然后使用代码的方式实现数据的解析,最后展示解析成果以及如何将数据转换成空间WKT数据。关于JAVA解析KML的博客和资料有不少,但是KMZ文件的还是比较稀少的,算是作为网文的一种补充,供各位朋友在工作中解析KMZ文件有一个参考。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。
博文在编写过程中参考了以下博文:
1、什么是 KMZ 文件?。



评论记录:
回复评论: