无人机(UAV)影像处理最佳实践

1、前言

数字孪生(Data Twins)概念已经在报端见诸多年,但是在制造业、能源化工、流域治理等还是最底层的搭建和展示。

回头看看,当初热火朝天炒的那波人和政策理论已经烟消云散,如今激不起半点涟漪,传统还是传统,并没有被数字信息革了命。

不说经济的下行原因,每个参与过其中的在思考:是什么让我们龃龉不前!!!

是技术跟不上理论?亦或是从来没有共识?

2、现状

在XX已有的安管应急项目中,几乎所有的厂区都涉及到三维数字模型建设,由于目前XX不具备实景三维和BIM建模能力,主要是通过第三方赋能。

同样在重要的人员定位模块,硬件完全依赖第三方,软件受限于第三方平台供给,结果参差不齐,疲于交付。

3、无人机(UAV)建模流程

一般建模流程是:前期规划》外业》内业》成果》交付

具体包括外业的无人机(UAV)航拍、外业量测采集、素材收集,然后是内业的处理:空片处理、空三测量、几何校正,最后输出成果:如DOM正射影像、DSM、DEM、实景三维倾斜摄影模型、BIM模型等

4、最佳实践

指的是如何将无人机(UAV)影像处理(ArcGIS Pro中操作),进而准确发布到Geoserver上,提供正常稳定OGC服务:如WMS、WMTS等,

具体如下:

注意:这里不提供特殊的多光谱无人机影像处理,譬如融合、镶嵌、匀色匀光等

(1)掩膜裁剪(extract by mask)

基于企业信息中范围(接口返回的WKT【Well-Known Text】获取,或者是从PosrgreSQL数据库中的poi_polygon表,使用st_astext导出,poi_type为11),

使用extract by mask工具,注意环境选择snap to this raster.

(2)坐标转换、影像压缩

使用copy raster在其中配置输出坐标为WGS1984、格式为TIFF、压缩算法为LZW、位深为16Bit(geoserver能准确识别无数据区)

(3)波段提取(extract bands)

使用raster function中的extract bands,选择composite形式为RGB

(4)geoserver发布

影像放置geoserver的data目录下,其他路径可能有权限等异常报错

(5)GeoWebCache生成

使用批处理数量不能超过机器核数,推荐5以下,坐标为ESPG:4326,格式为png,级别15-21

什么是texture函数

在着色器语言中,如GLSL(OpenGL Shading Language),texture2D 是用于在纹理上进行采样的函数。它的参数通常包括两个:

1
vec4 texture2D(sampler2D sampler, vec2 textureCoordinates);

其中:

  • sampler2D sampler:表示纹理采样器,它是一个指向纹理对象的引用。这个参数告诉着色器在哪个纹理上进行采样。

  • vec2 textureCoordinates:表示纹理坐标,用于指定在纹理上的位置。纹理坐标通常是二维的,范围在 [0.0, 1.0] 之间。通常 (0, 0) 表示纹理的左下角,(1, 1) 表示右上角。

texture2D 函数返回一个 vec4 类型的颜色值,其中包含了在指定纹理坐标位置处采样得到的颜色信息。这个颜色值通常包括红色(R)、绿色(G)、蓝色(B)和 alpha 通道(A)的分量。

例如,在一个简单的片元着色器中,你可能会使用 texture2D 函数来获取纹理上某个位置的颜色:

1
2
3
4
5
6
7
8
9
uniform sampler2D myTexture;  // 纹理采样器
varying vec2 vTextureCoord; // 从顶点着色器传递过来的纹理坐标

void main() {
vec4 color = texture2D(myTexture, vTextureCoord);
// 使用 color 进行进一步的处理
// ...
gl_FragColor = color;
}

在这个例子中,myTexture 是一个纹理采样器,vTextureCoord 是从顶点着色器传递过来的纹理坐标。texture2D(myTexture, vTextureCoord) 用于在这个纹理坐标处采样纹理,返回颜色值,然后可以在着色器中进行后续处理。

完美geoserver跨域解决方式(2024版)

因为tomcat容器本身或者geoserver的安全策略,在tomcat中部署geoserver,导致在其他应用使用geoserver地图服务会出现跨域现象。
通常会通过nginx的反向代理解决这个问题。

但是安装nginx反过来又会让流程变得繁琐,将大象装到冰箱里就不止三部了。

我们知道,GeoServer的项目是一个完整的JavaJ2EE)系统。

所以,基于geoserver本身配置可以来解决这个问题。

但是百度搜索的结果不是过时,就是毫无责任的、误导人的遍地复制粘贴,也说明现在搜索引擎的尴尬局面:blog论坛贴吧的时代一去不复返,robot协议更加苛刻,搜索爬虫处于严重饥饿状态。 所以我们拿到的结果显得匮乏,难以命中问题。

好在chatgpt解决了一些问题,但是使用成本和难以预料的胡说八道,又让我们不得不审视其专业性。

废话不再多说

简要说下就是在tomcat中部署完geoserver,能正常访问后,通过修改geoserverweb.xml文件,将两个jar包添加到geoserverlib目录,就能完美解决跨域问题,重点是找到不同geoserver所对应的jar版本

下面细说步骤:
1、修改geoserver下的web.xml(路径一般为:/tomcat/webapps/geoserver/WEB-INF/web.xml),将文件中有关cross-origin的注释放开,但是可能会找错地方,最终造成geoserver打不开,保险的方式是:在文件空行中(如大约150行左右)直接添加以下配置;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<filter>
<filter-name>cross-origin</filter-name>
<filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
<init-param>
<param-name>chainPreflight</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>allowedOrigins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>allowedMethods</param-name>
<param-value>GET,POST,PUT,DELETE,HEAD,OPTIONS</param-value>
</init-param>
<init-param>
<param-name>allowedHeaders</param-name>
<param-value>*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>cross-origin</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

2、接下来我们需要找到geoserver对应版本的jetty-servletsjetty-utiljar文件;

3、首先我们确认下载的geoserver版本,如下
geoserver版本

3、然后复制版本号,拼接到如下url并访问,
https://github.com/geoserver/geoserver/blob/版本号/src/pom.xml (如:https://github.com/geoserver/geoserver/blob/2.23.2/src/pom.xml);

4、Ctrl+F搜索网页内容:jetty.version,复制版本号;
jetty版本

5、将版本号复制以下两个url中:

(1)https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-servlets/版本号 (如:https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-servlets/9.4.51.v20230217)

(2)https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-util/版本号 (如:https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-util/9.4.51.v20230217)

6、单击jar下载文件;
jetty-servletsjetty-util

7、将jetty-servletsjetty-utiljar包拷贝到geoserver下的lib目录,路径为:tomcat/webapps/geoserver/WEB-INF/lib/

8、重启geoserver,暨重启tomcat,解决跨域。

航班数据分享

近期抓取了一些航班数据,分享一下。

1
2
3
数据范围:全国(境内,不包括港澳台)
日期范围:20230320-20230327
航班数量:58987条

数据预览
关注右侧公众号zyouzz回复”航班”获取下载,

有偿提供近期或者其他日期航班数据(全国、世界均可)

如有需要,请于公众号zyouzz留言

WebGL若干原理摘抄

Shader

shader,即着色器,分为顶点着色器(Vertex Shader)片元着色器(Fragment Shader)几何着色器(Geometry shader)计算着色器(Compute shader)细分曲面着色器(Tessellation or hull shader),其中可编程的是顶点着色器和片元着色器。至于它们的定义网上可以找到很多,但对于小白来讲看完还是一脸懵逼,我们需要一种通俗易懂的解释,这才符合深入浅出的精髓。

在知乎上找到一段解释,感觉还不错:

1
2
3
4
5
6
7
8
9
10
11
12
13
当我们在屏幕上绘制或显示一些物体时,
这些物体的显示形式是图元(Primitive)或者网格(Mesh),
比如游戏中一个几何模型角色或一个贴在网格上的纹理角色,
比如我们做阴影效果时先绘制网格再计算阴影,
比如一个发射物体发射前需要先绘制该物体外形网格。
这些物体都可归结为网格,它可被分解为图元,即图元是网格的基本单位。
图元有三角形、直线或点。
当我们在屏幕上画一个三角形时,首先要绘制顶点,
因为网格由顶点组成,此时就要用到顶点着色器(Vertex shader),
将需要到顶点信息给顶点着色器,以显示顶点信息;
其次是在这些顶点组成的区域之间填充颜色,
此时用到像素着色器(Pixel shader)或片元着色器(Fragment shader),
片段(Fragment)有助于定义像素的最终颜色。

shader

简单来说渲染流程如下:
顶点数据(Vertices) > 顶点着色器(Vertex Shader) > 图元装配(Assembly) > 几何着色器(Geometry Shader) > 光栅化(Rasterization) > 片元着色器(Fragment Shader) > 逐片元处理(Per-Fragment Operations) > 帧缓冲(FrameBuffer)

最后经过双缓冲的交换(SwapBuffer),渲染内容就显示到了屏幕上。从流程中我们可以看到,顶点着色器之后是图元装配, 图元装配通俗讲就是把图形放置到坐标系中。在片元着色器之前是 光栅化,光栅化是将图形投影到屏幕上,把图形栅格化成一个个的像素点,一个像素点也就是一个片元。在片元着色器之后是逐片元处理, 逐片元处理即填充颜色。再说白一点,顶点着色器负责坐标位置,片元着色器负责填充颜色。好吧,这个说法不一定很严谨,但一定足够浅显,如果我说的不对也欢迎拍砖。

域名包括二级路径,配置VUE中vite

通常情况下我们将vue打包后,直接放置nginx或者tomcat等容器里,配置下相关路径文件就可以访问了。
但是当我们使用域名的二级路径访问时,如https://abc.com/cesium/,会报404错误,这是因为一些静态文件路径为域名+文件形式,导致找不到文件,例如index.html中的文件为/assets/index-9a873818.js,导致直接拼接了域名。
接下来需要在工程中做相关配置
1、增加开发环境文件.env.development,添加以下内容

1
2
##开发环境
VITE_BASE_PATH='/'

2、增加生产环境文件.env.production,添加以下内容

1
2
#生产环境
VITE_BASE_PATH=/cesium/

3、修改package.json中的devbuild命令为:

1
2
"dev": "vite serve --mode development",
"build": "vite build --mode production",

4、安装dotenv模块,

1
yarn add dotenv

5、在vite.config.js中增加

1
2
3
4
5
6
import dotenv from 'dotenv';
dotenv.config({ path: `.env.${process.env.NODE_ENV}` });
//添加base
export default defineConfig({
base: process.env.VITE_BASE_PATH,
})

另外附nginx一般配置,

1
2
3
4
5
location cesium/ {
root html/cesium/;
try_files $uri $uri/ /index.html;
index index.html index.htm;
}

注意文件夹名要和域名二级路径一致,不然需要在index.html前指定文件夹名称,如下

1
2
3
4
5
location cesium/ {
root html/test/;
try_files $uri $uri/ test/index.html;
index index.html index.htm;
}

Cesium拾取坐标三种方式(摘抄)

首先基于事件触发捕捉位置信息,一般是Cesium.ScreenSpaceEventType.MOUSE_MOVE,也可以是Cesium.ScreenSpaceEventType.LEFT_CLICK或者Cesium.ScreenSpaceEventType.RIGHT_CLICK

1
2
3
4
this.mouseHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
this.mouseHandler.setInputAction((movement) => {
// 添加方法
},Cesium.ScreenSpaceEventType.MOUSE_MOVE)

根据场景加载数据和需求,大部分情况下,我们拾取的是模型表面位置,所以使用方法一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 方式一: 从场景的深度缓冲区中拾取相应的位置,可以采集entity,primitive,3dtile
// 当拾取区域无entity,primitive,3dtile时,将返回一个无法预料的坐标(标准椭球面以下,无法使用,无法预料)
let cartesianPosition = viewer.scene.pickPosition(movement.position);
if (cartesian3) {
// 下面两个都行
// let cartographic3 = Cesium.Cartographic.fromCartesian(cartesianPosition);
let cartographic3 = ellipsoid.cartesianToCartographic(cartesianPosition);
this.pickInfoOption3.lng = Cesium.Math.toDegrees(cartographic3.longitude);
this.pickInfoOption3.lat = Cesium.Math.toDegrees(cartographic3.latitude);
this.pickInfoOption3.height = cartographic3.height;
}
// 方式二:获取当前点击视线与地球表面相交的坐标(有地形时候均可用,无地形时高程几乎为0)(无视entity、3dtile,不能拾取其表面坐标)
let ray = viewer.scene.camera.getPickRay(movement.position);
let cartesian1 = viewer.scene.globe.pick(ray, viewer.scene);
if (cartesian1) {
let cartographic1 = Cesium.Ellipsoid.WGS84.cartesianToCartographic(cartesian1);
this.pickInfoOption1.lng = Cesium.Math.toDegrees(cartographic1.longitude);
this.pickInfoOption1.lat = Cesium.Math.toDegrees(cartographic1.latitude);
this.pickInfoOption1.height = cartographic1.height;
}
// 方式三:获取当前点击视线与标准椭球面相交处的坐标,无高程(恒为0)
let cartesian2 = viewer.camera.pickEllipsoid(movement.position, ellipsoid);
if (cartesian2) {
let cartographic2 = ellipsoid.cartesianToCartographic(cartesian2);
this.pickInfoOption2.lng = Cesium.Math.toDegrees(cartographic2.longitude);
this.pickInfoOption2.lat = Cesium.Math.toDegrees(cartographic2.latitude);
this.pickInfoOption2.height = cartographic2.height;
}

postman设置collection全局token

平时在postman调试接口多,如果过了一段时间后,token会失效,每个请求需要更换token,挺麻烦的,我们可以根据在postman配置collection下的全局token;
具体如下
1、打开Postman并创建一个新的Collection。
Collection
2、在Collection的”Authorization”选项卡中选择适当的授权方式(例如Bearer Token或OAuth 2.0)。
3、在”Token”字段中,使用双大括号({{}})将Token包裹起来,形成一个环境变量。例如:{{token}}。
token
4、点击右上角的“眼睛”按钮,以便在请求中查看环境变量的值。
环境变量
5、现在,您需要在Postman的环境中设置Token的值。在Postman左上角的”Manage Environments”(管理环境)按钮旁边,点击下拉菜单按钮,并选择”Add”(添加)。
6、输入环境名称,例如”Token Environment”,然后点击”Add”(添加)。
Add
7、在环境变量列表中,添加一个新的变量,键为”token”,值为您的Token值。
Add
8、点击”Save”(保存)以保存环境变量。
9、注意检查请求中Authorization是否为inherit auth from parent
inherit auth from parent

time is start, time is end, time is nothing

time can be done,
we come from mote,
burn, burn, burn,
come to the end,
ash,
air,
element,
from order to chaos,
hard to return origin,
sadness,
tears,
flow freely,
I am you,
you are me,
one day,
we will be together,
smile,
warm,
time is so long,
swear it,
Believe it,
accept it.
–ZhongYuan(MidYear) James.