时间跨度
2024-07=》**
目录
- 3dtiles加载
- rtsp与rtmp
- ijkplayer源码中对于rtsp的处理是怎样的
- js如何请求rtsp协议的地址进行解析
package.json
中的engines
- cesium personal practicing
- 优化3dtiles加载
- map demo
- jecloud的多选样式和单选样式不一样
- 安卓项目
- rtsp/express/ffmpeg/flv项目demo
- 大华摄像头插件,集成控制,云台,播放等功能
- api
- 链接
- 感慨
- 某些能让你想起什么的代码块
3dtiles加载
3D Tiles是一种开放规范,旨在优化大规模3D地理数据的可视化和传输.它允许您将大量的3D模型、点云和其他地理数据分割成更小的部分,以便在Web浏览器中高效地加载和渲染.
3D Tiles 文件的特性:
- 层级化数据结构(Hierarchy of Tiles): 支持逐步加载和细化显示.
- 二进制数据格式: 提供高效的存储和传输.
- 元数据支持: 可以包含丰富的元数据,比如属性和样式.
加载3D Tiles的解决方案
1. CesiumJS
CesiumJS是一个开源JavaScript库,用于在Web浏览器中渲染3D地理数据.它原生支持3D Tiles.
2. Three.js
虽然Three.js并没有原生支持3D Tiles,但可以使用一些插件或自定义解析器来加载3D Tiles.
- 插件: 可以使用three-3d-tiles插件,它是一个用来在Three.js中加载和渲染3D Tiles的库.
- 文档: three-3d-tiles 文档
3. Mapbox GL JS
Mapbox GL JS是一个开源的JavaScript库,用于在Web浏览器中呈现交互式地图.虽然它主要用于2D地理数据,但也可以通过一些方法支持3D Tiles.
- 插件: 可以使用
mapbox-3dtiles
插件,它是一个非官方的插件,提供了在Mapbox GL JS中加载3D Tiles的功能. - 文档: mapbox-3dtiles 文档
具体解决方案文档
-
CesiumJS:
-
Three.js:
-
Mapbox GL JS:
这些文档和插件可以帮助你在不同的Web环境中加载和渲染3D Tiles文件.根据你的具体需求和技术栈选择最合适的方案.
rtsp与rtmp
ijkplayer源码中对于rtsp的处理是怎样的
好的,我们来详细分析一下 ijkplayer 源码中对于 RTSP 的处理方式.
ijkplayer 中 RTSP 的处理主要涉及以下几个关键部分:
1. 协议解析:
- ijkplayer 利用 FFmpeg 的 RTSP 解码器 (
libavformat
) 对 RTSP 流进行解析.1 - 解码器会根据 RTSP 协议的规范,解析流中的各种信息,例如:
- 视频和音频编码信息
- 分辨率、帧率等参数
- 媒体数据包的传输方式 (UDP 或 TCP)
- 播放控制指令等
2. 数据传输:
- ijkplayer 可以根据 RTSP 流的设置,选择合适的传输方式.1
3. 数据解码:
- 解码器会根据解析出的信息,对接收到的媒体数据包进行解码.1
- ijkplayer 支持多种视频和音频解码器,例如 H.264、H.265、AAC、MP3 等.1
- ijkplayer 会优先使用硬件解码器 (例如 MediaCodec 或 VideoToolbox) 来提高解码效率.1
4. 视频渲染:
5. 音频输出:
6. 播放控制:
- ijkplayer 可以通过 RTSP 协议的控制指令来控制视频的播放,例如暂停、播放、快进、快退等.1
总的来说,ijkplayer 使用 FFmpeg 的 RTSP 解码器来解析 RTSP 流,并利用其解码和渲染功能来播放视频和音频. 1 开发者可以通过配置 RTSP 协议参数和使用 ijkplayer 提供的 API 来定制播放器的行为.1
如果您想更深入地了解 ijkplayer 源码中 RTSP 的实现细节,您可以参考 ijkplayer 的官方文档和代码库.1
js如何请求rtsp协议的地址进行解析
JavaScript 本身并不直接支持 RTSP 协议,因为浏览器环境通常不提供对 RTSP 的支持.RTSP(Real-Time Streaming Protocol)通常用于网络摄像头、安防监控等实时视频流的传输.在浏览器中处理 RTSP 流,需要通过其他方式间接实现,例如通过中间服务器进行协议转换,或使用专门的库.
以下是一些常见的方法来在浏览器中处理 RTSP 流:
方法一:通过流媒体服务器进行协议转换
使用流媒体服务器(如 Wowza、VLC、FFmpeg、GStreamer)将 RTSP 流转换为浏览器支持的协议(如 HTTP、HLS、DASH),然后在前端使用视频播放器播放.
使用 FFmpeg 进行协议转换
-
安装 FFmpeg:确保你已经在服务器上安装了 FFmpeg.
-
启动 FFmpeg 进行转码和转发:
ffmpeg -i rtsp://your-rtsp-url -f flv rtmp://your-rtmp-server/live/stream
- 在前端使用 HTML5 video 标签或第三方播放器播放:
<video id="video" controls autoplay>
<source
src="http://your-http-server/live/stream.m3u8"
type="application/x-mpegURL"
/>
Your browser does not support the video tag.
</video>
方法二:使用 WebRTC
使用 WebRTC 库将 RTSP 流转换并传输到浏览器.WebRTC 是一个实时通信协议,浏览器原生支持.
-
设置 WebRTC 媒体服务器(如 Janus、Kurento):
- 配置服务器接收 RTSP 流,并通过 WebRTC 将其传输到浏览器.
-
前端代码: 使用 WebRTC API 接收和播放视频流.
<!doctype html>
<html>
<head>
<title>WebRTC RTSP Stream</title>
</head>
<body>
<video id="video" autoplay></video>
<script src="https://janus.conf.meetecho.com/janus.js"></script>
<script>
const janusServer = "wss://your-janus-server:8989";
const streamId = 1;
const janus = new Janus({
server: janusServer,
success: function () {
janus.attach({
plugin: "janus.plugin.streaming",
success: function (pluginHandle) {
const streaming = pluginHandle;
streaming.send({ message: { request: "watch", id: streamId } });
},
onmessage: function (msg, jsep) {
if (jsep !== undefined && jsep !== null) {
streaming.createAnswer({
jsep: jsep,
media: { audioSend: false, videoSend: false },
success: function (jsep) {
streaming.send({
message: { request: "start" },
jsep: jsep,
});
},
});
}
},
onremotestream: function (stream) {
document.getElementById("video").srcObject = stream;
},
oncleanup: function () {
console.log("Stream ended");
},
});
},
error: function (error) {
console.error("Janus error:", error);
},
destroyed: function () {
console.log("Janus session ended");
},
});
</script>
</body>
</html>
方法三:使用库如 JSMpeg
JSMpeg 是一个基于 JavaScript 的库,可以播放通过 WebSockets 传输的 MPEG-TS 流.
- 设置 WebSocket 服务器:使用 FFmpeg 将 RTSP 流转换为 MPEG-TS 并通过 WebSocket 传输.
ffmpeg -i rtsp://your-rtsp-url -f mpegts -codec:v mpeg1video -s 800x600 -b:v 1000k -bf 0 http://your-websocket-server:8081
- 前端代码:
<!doctype html>
<html>
<head>
<title>RTSP Stream with JSMpeg</title>
<script src="https://cdn.jsdelivr.net/npm/jsmpeg@0.2.1/jsmpeg.min.js"></script>
</head>
<body>
<canvas id="videoCanvas"></canvas>
<script>
const url = "ws://your-websocket-server:8081";
const canvas = document.getElementById("videoCanvas");
const player = new JSMpeg.Player(url, { canvas: canvas });
</script>
</body>
</html>
总结
直接在浏览器中请求和解析 RTSP 流是比较困难的,通常需要通过中间服务器进行协议转换,或使用 WebRTC 进行传输.使用上述方法,可以将 RTSP 流转换为浏览器支持的格式,并在前端播放.根据具体需求选择适合的方法,确保实现实时流媒体播放.
package.json
中的engines
在 package.json
文件中,engines
字段用于指定你的项目依赖的运行环境的版本.这包括 Node.js 和 npm 的版本等.通过定义这些版本,你可以确保在特定的环境中运行你的项目,从而避免由于版本不兼容导致的问题.
以下是如何在 package.json
文件中使用 engines
字段的示例:
{
"name": "your-project-name",
"version": "1.0.0",
"description": "Your project description",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"nodemon": "^2.0.7"
},
"engines": {
"node": ">=14.0.0",
"npm": ">=6.0.0"
}
}
在这个示例中:
node: ">=14.0.0"
:指定项目需要 Node.js 版本 14.0.0 或更高版本.npm: ">=6.0.0"
:指定项目需要 npm 版本 6.0.0 或更高版本.
解释
"node": ">=14.0.0"
:这意味着项目依赖 Node.js 版本 14.0.0 或更高版本.你可以使用其他版本约束,比如"14.x"
以表示任何 14.x 的版本,或者"14.17.0"
以指定特定的版本."npm": ">=6.0.0"
:这意味着项目依赖 npm 版本 6.0.0 或更高版本.
版本约束
engines
字段支持多种版本约束语法,常见的包括:
"^14.0.0"
:兼容版本 14.0.0,但不包括 15.0.0."~14.0.0"
:大约 14.0.0,即 14.0.x,但不包括 14.1.0.">=14.0.0"
:版本 14.0.0 或更高版本."14.x"
:14 的任何小版本.
强制执行版本要求
虽然 engines
字段提供了版本约束,但默认情况下不会强制执行这些要求.为了强制执行,可以使用 engine-strict
设置或相应的工具.
在 .npmrc
文件中启用 engine-strict
模式:
你可以在项目的 .npmrc
文件中添加以下行以强制执行引擎版本要求:
engine-strict=true
使用 yarn
如果你使用 yarn
作为包管理器,yarn
会自动遵循 engines
字段,并在不满足版本要求时给出警告.
总结
通过在 package.json
文件中使用 engines
字段,你可以指定项目运行所需的 Node.js 和 npm 版本.这有助于确保项目在特定的运行环境中运行,避免版本不兼容问题.可以使用多种版本约束语法来定义所需的版本范围,并通过 engine-strict
设置或使用 yarn
来强制执行这些版本要求.
cesium personal practicing
astro page 多页
carMove
在你的代码中,你正在使用Cesium来加载和显示多个CZML文件,这些文件描述了一个多部分的车辆模型。为了确保这些模型在适当的时间段内被加载,你设置了一个时间偏移量来决定何时请求每个部分的数据。
首先,确保你的CZML文件的路径是正确的,并且这些文件的内容符合CZML的标准。CZML(Cesium Language)是一种JSON格式,用于描述时间动态的地理空间数据。每个CZML文件应包含一个或多个实体,描述它们的属性和时间动态。
在你的代码中,partsToLoad
数组定义了三个部分,每个部分都有一个加载的时间范围。procesPart
函数负责处理每个CZML文件的加载。你在viewer.clock.onTick
事件中检查当前的时间偏移量,并根据这个偏移量决定是否需要加载某个部分。
以下是一些建议和注意事项,以确保CZML文件能够顺利加载:
-
确保CZML文件的有效性:检查CZML文件的格式和内容,确保它们符合CZML标准。可以使用在线工具或Cesium的文档来验证。
-
处理加载错误:在
procesPart
函数中,可以添加错误处理逻辑,以便在加载失败时进行调试。例如:dataSource .process(part.url) .then(function () { part.loaded = true; if (!viewer.trackedEntity) { viewer.trackedEntity = entity = dataSource.entities.getById("Vehicle"); } }) .catch(function (error) { console.error("加载CZML文件失败:", error); });
-
优化加载顺序:如果某些部分依赖于其他部分,确保它们的加载顺序是正确的。可以在
partsToLoad
中调整它们的顺序,或者在加载时添加依赖关系检查。 -
调试信息:在开发过程中,可以在控制台中输出调试信息,以便跟踪加载状态。例如:
console.log(`请求加载: ${part.url}`);
-
使用Cesium的事件:利用Cesium提供的事件机制,可以在加载完成或失败时触发相应的回调,更新UI或执行其他操作。
通过这些步骤,你可以更好地管理CZML文件的加载过程,确保在合适的时间展示相应的模型部分。这样一来,用户在观看时就能体验到流畅的动态效果。
react tsx page 使用web组件
v0 to generate component (使用react实现cesium人员定位)
优化3dtiles加载
const viewer = new Cesium.Viewer("cesiumContainer", {
scene3DOnly: true,
maximumRenderTimeChange: Infinity,
sceneModePicker: false,
// 禁用默认的图层选择器
baseLayerPicker: false,
// 禁用默认的缩放控件
animation: false,
// 禁用默认的图层选择器
timeline: false,
requestRenderMode: true,
// 在 Viewer 初始化时直接设置 globe 属性
// globe: false,
});
// 禁用天空盒和大气层
viewer.scene.skyBox.show = false;
viewer.scene.skyAtmosphere.show = false;
// 抗锯齿
viewer.scene.postProcessStages.fxaa.enabled = true;
const url = "/assets/models/3dtiles/1/tileset.json";
const tileset = await Cesium.Cesium3DTileset.fromUrl(url, {
preloadWhenHidden: true,
dynamicScreenSpaceError: true,
});
viewer.scene.primitives.add(tileset);
// 加载模型后,设置相机视角
viewer.zoomTo(tileset).then(() => {});
map demo
jecloud的多选样式和单选样式不一样
源码中
multiple ? (
<Panel.Item ref={$resultPanel} region="right" split collapsible size="232">
<Grid
size="small"
class="je-grid-func-select-result"
columns={gridColumns}
store={resultStore}
bodyBorder
draggable
v-slots={{
tbar() {
return (
<div class="title">
已选数据<span class="red">({resultStore.data.length}条)</span>
<i
class="icon fal fa-chevron-right"
onClick={() => {
$resultPanel.value.setExpand(false);
}}
></i>
</div>
);
},
}}
/>
</Panel.Item>
) : null,
直接就一个multiple控制了右边已选择,难绷
安卓项目
keystore
在 Android 项目中生成 keystore 文件,您需要使用 Java Development Kit (JDK) 中自带的 keytool
工具。
以下是生成 keystore 的步骤:
-
打开命令行工具
- 在 Windows 上,您可以使用
cmd
或PowerShell
。 - 在 macOS 或 Linux 上,您可以使用
Terminal
。
- 在 Windows 上,您可以使用
-
进入 JDK 的
bin
目录- 您可以使用
cd
命令进入该目录,例如:cd C:\Program Files\Java\jdk-17.0.1\bin
- 请将
C:\Program Files\Java\jdk-17.0.1\bin
替换为您实际安装 JDK 的路径。
- 您可以使用
-
执行以下命令生成 keystore 文件
keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
- 将
my-release-key.keystore
替换为您想要的 keystore 文件名。 - 将
alias_name
替换为您想要的别名。 keyalg
指定密钥算法,这里使用 RSA。keysize
指定密钥长度,这里使用 2048 位。validity
指定有效期,这里设置为 10000 天。
- 将
-
根据提示输入信息
- 您需要输入 keystore 和密钥的密码,以及一些个人信息。
- 这些信息将用于生成证书,请确保妥善保管密码。
-
生成 keystore 文件
- 命令执行成功后,将在当前目录下生成名为
my-release-key.keystore
的 keystore 文件。
- 命令执行成功后,将在当前目录下生成名为
注意:
rtsp/express/ffmpeg/flv项目demo
index.js
这段代码是一个使用 Node.js 和 Express 框架构建的简单视频流服务器,主要用于将 RTSP(实时流协议)视频流转换为 FLV(Flash 视频)格式并通过 HTTP 提供给客户端。下面是对代码的详细解释:
主要目标
- 创建一个 HTTP 服务器:使用 Express 框架来处理 HTTP 请求。
- 处理视频流:使用 FFmpeg 将 RTSP 视频流转换为 FLV 格式,并将其通过 HTTP 发送给客户端。
- 事件管理:使用 EventEmitter 来管理视频流数据的传输。
代码逐行解释
-
导入模块:
const { spawn } = require("child_process"); const { EventEmitter } = require("events"); const express = require("express"); const path = require("path");
spawn
:用于创建子进程,这里用于启动 FFmpeg。EventEmitter
:用于创建事件发射器,以便在数据流中进行事件管理。express
:用于创建 HTTP 服务器。path
:用于处理文件路径。
-
初始化应用和服务器:
const app = express(); const server = require("http").Server(app); const PORT = 5500; const RTSP_URL = "";
- 创建一个 Express 应用实例。
- 创建一个 HTTP 服务器并指定端口(5500)。
RTSP_URL
是 RTSP 视频流的 URL,当前为空。
-
初始化事件发射器和首个数据块:
const emitters = {}; const firstChunks = {};
emitters
用于存储每个视频流的事件发射器。firstChunks
用于存储每个视频流的首个数据块。
-
初始化事件发射器的函数:
const initEmitter = feed => { if (!emitters[feed]) { emitters[feed] = new EventEmitter().setMaxListeners(0); } return emitters[feed]; };
- 如果指定的
feed
(视频流标识符)没有对应的事件发射器,则创建一个新的事件发射器并返回。
- 如果指定的
-
初始化首个数据块的函数:
const initFirstChunk = (feed, firstBuffer) => { if (!firstChunks[feed]) { firstChunks[feed] = firstBuffer; } return firstChunks[feed]; };
- 如果指定的
feed
没有首个数据块,则将其初始化为传入的firstBuffer
。
- 如果指定的
-
启动服务器:
console.log(`Starting Express Web Server on Port ${PORT}`); server.listen(PORT);
- 在控制台输出服务器启动信息,并开始监听指定的端口。
-
提供静态文件:
app.use("/libs", express.static(path.join(__dirname, "../../web/libs"))); app.use("/", express.static(__dirname));
- 设置静态文件服务,允许访问
libs
目录和当前目录下的文件。
- 设置静态文件服务,允许访问
-
主页路由:
app.get("/", (req, res) => { res.sendFile(path.join(__dirname, "index.html")); });
- 当访问根路径时,返回
index.html
文件。
- 当访问根路径时,返回
-
FLV 视频流路由:
app.get(["/flv", "/flv/:feed/s.flv"], (req, res) => { const feed = req.params.feed || "1"; req.Emitter = initEmitter(feed); ... });
- 处理
/flv
和/flv/:feed/s.flv
路由,初始化事件发射器并设置响应头。
- 处理
-
设置响应头和初始化首个数据块:
res.setHeader("Content-Type", "video/x-flv"); res.setHeader("Access-Control-Allow-Origin", "*"); res.write(initFirstChunk(feed, Buffer.alloc(0))); // Initialize with empty buffer
- 设置内容类型为 FLV 视频,并允许跨域请求。
- 初始化首个数据块为空缓冲区。
-
处理数据流:
const contentWriter = buffer => res.write(buffer); req.Emitter.on("data", contentWriter); res.on("close", () => { req.Emitter.removeListener("data", contentWriter); });
- 当接收到数据时,将其写入响应中,并在连接关闭时移除监听器。
-
启动 FFmpeg 进程:
console.log("Starting FFMPEG"); let ffmpegArgs = [ "-i", RTSP_URL, "-c:v", "libx264", "-preset", "fast", "-crf", "23", "-an", "-f", "flv", "pipe:1", ];
- 设置 FFmpeg 的参数以从 RTSP URL 读取视频流并将其转换为 FLV 格式。
-
检查 RTSP URL:
if (RTSP_URL.includes("rtsp://")) { ffmpegArgs.unshift("-rtsp_transport", "tcp"); }
- 如果 RTSP URL 包含
rtsp://
,则使用 TCP 作为传输协议。
- 如果 RTSP URL 包含
-
执行 FFmpeg:
console.log(`Executing: ffmpeg ${ffmpegArgs.join(" ")}`); const ffmpeg = spawn("ffmpeg", ffmpegArgs, { stdio: ["pipe", "pipe", "pipe"], });
- 输出执行的 FFmpeg 命令,并启动 FFmpeg 进程。
-
处理 FFmpeg 进程的关闭和错误:
ffmpeg.on("close", () => { console.log("FFmpeg process exited"); }); ffmpeg.stderr.on("data", data => { console.error(`FFmpeg error: ${data.toString()}`); });
- 监听 FFmpeg 进程的关闭事件和错误输出。
-
处理 FFmpeg 输出的数据:
ffmpeg.stdout.on("data", buffer => { initFirstChunk("1", buffer); initEmitter("1").emit("data", buffer); });
- 当 FFmpeg 输出数据时,初始化首个数据块并通过事件发射器发送数据。
总结
这段代码实现了一个简单的 HTTP 视频流服务器,能够将 RTSP 视频流转换为 FLV 格式并通过 HTTP 提供给客户端。它使用了 Node.js 的子进程功能来运行 FFmpeg,并通过事件发射器来管理视频流数据的传输。
html
这段代码是一个 HTML 页面,用于实现一个简单的 FLV 视频直播播放器,支持播放、暂停、停止、快退、全屏、录制等功能。以下是对代码的详细解释:
HTML 结构
-
文档类型和语言:
<!doctype html> <html lang="zh-CN"></html>
- 指定文档类型为 HTML5,并设置页面语言为中文(简体)。
-
头部信息:
<head> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta charset="UTF-8" /> <title>FLV直播测试</title> <script src="http://cdn.shinobi.video/js/flv.min.js"></script> ... </head>
- 设置浏览器兼容性和字符编码。
- 页面标题为“FLV直播测试”。
- 引入了 FLV 播放器的 JavaScript 库。
-
样式:
<style> body { display: flex; flex-direction: column; align-items: center; background-color: #f0f0f0; font-family: Arial, sans-serif; } ... </style>
- 使用 CSS 设置页面的布局和样式,包括背景色、字体、视频元素的样式等。
主体内容
-
视频元素:
<video id="videoElement" controls></video>
- 创建一个视频元素,ID 为
videoElement
,并启用默认控制。
- 创建一个视频元素,ID 为
-
控制面板:
<div id="controls"> <button id="playButton">播放</button> <button id="pauseButton">暂停</button> ... </div>
- 创建一个控制面板,包含多个按钮用于控制视频播放、暂停、停止、快退、全屏和录制等功能。
JavaScript 功能实现
-
获取元素:
const videoElement = document.getElementById("videoElement"); const playButton = document.getElementById("playButton"); ...
- 获取页面中的视频元素和控制按钮的引用。
-
FLV 播放器初始化:
if (flvjs.isSupported()) { flvPlayer = flvjs.createPlayer({ type: "flv", isLive: true, url: "/flv/1/s.flv", }); ... }
- 检查浏览器是否支持 FLV 播放器,如果支持,则创建一个 FLV 播放器实例,设置为直播模式,并指定视频流的 URL。
-
事件监听器:
const addEventListeners = () => { playButton.addEventListener("click", () => flvPlayer.play()); pauseButton.addEventListener("click", () => flvPlayer.pause()); ... };
- 为各个控制按钮添加事件监听器,以实现相应的功能,如播放、暂停、停止等。
-
快退功能:
const rewindCustom = seconds => { videoElement.currentTime = Math.max(0, videoElement.currentTime - seconds); };
- 定义一个快退函数,允许用户将视频回退指定的秒数。
-
全屏切换:
const toggleFullScreen = () => { if (!document.fullscreenElement) { videoElement.requestFullscreen().catch(err => console.log(err)); } else { document.exitFullscreen(); } };
- 定义一个函数用于切换全屏模式。
-
录制功能:
const startRecording = () => { recordedChunks = []; const stream = videoElement.captureStream(); mediaRecorder = new MediaRecorder(stream); ... };
- 定义开始录制的函数,使用
MediaRecorder
API 录制视频流,并在录制停止时生成一个可下载的录制文件。
- 定义开始录制的函数,使用
-
停止录制:
const stopRecording = () => { mediaRecorder.stop(); recordButton.disabled = false; stopRecordButton.disabled = true; };
- 定义停止录制的函数,停止媒体录制并更新按钮状态。
总结
这段代码实现了一个功能丰富的 FLV 视频直播播放器,用户可以通过简单的按钮控制视频的播放、暂停、快退、全屏和录制等功能。通过使用 FLV.js 库和 MediaRecorder API,页面能够处理实时视频流并提供录制功能,增强了用户体验。
大华摄像头插件,集成控制,云台,播放等功能
api
Cesium.Transforms.eastNorthUpToFixedFrame
devv
Cesium.Transforms.eastNorthUpToFixedFrame
是 Cesium 中的一个方法,用于生成一个将东、北、上(East, North, Up)坐标系转换为固定框架(Fixed Frame)坐标系的变换矩阵。
详细说明:
-
坐标系:
- 东、北、上坐标系:这是一个局部坐标系,通常用于描述相对于某个点的方向。东(East)指向地球的东边,北(North)指向地球的北边,上(Up)指向地球的上方。
- 固定框架坐标系:这是一个全局坐标系,通常与地球的中心对齐。
-
用途:
- 这个方法常用于在 Cesium 中进行对象的定位和方向设置。通过将局部坐标系转换为全局坐标系,可以更方便地在三维空间中放置和旋转对象。
方法签名:
Cesium.Transforms.eastNorthUpToFixedFrame(origin, result);
- 参数:
origin
:表示局部坐标系的原点,通常是一个Cartesian3
类型的坐标。result
(可选):一个Matrix4
类型的对象,用于存储返回的变换矩阵。如果不提供,方法将返回一个新的Matrix4
对象。
示例:
以下是一个简单的示例,展示如何使用 eastNorthUpToFixedFrame
方法:
const origin = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);
const transformMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
// 使用变换矩阵进行对象的定位
const modelMatrix = Cesium.Matrix4.multiplyByTranslation(
transformMatrix,
new Cesium.Cartesian3(0, 0, 0),
new Cesium.Matrix4()
);
在这个示例中,origin
是一个地理坐标(经度、纬度和高度),transformMatrix
是将该点的东、北、上坐标系转换为固定框架坐标系的变换矩阵。通过这个变换矩阵,你可以在 Cesium 中正确地放置和旋转对象。
链接
感慨
抠搜老板/老板娘组合
某些能让你想起什么的代码块
jecloud action操作列自定义为更多
,即展开/收起逻辑
Click to expand
const { $func, $grid, column, row } = EventOptions;
const { ref, h, defineComponent } = JE.useVue();
const level = row["TICKET_LEVEL"] === "DTZY";
const buttons = $func.getActionButtons().filter(item => {
let show = !item.hidden;
if (item.code == "delay" && !level) {
show = false;
}
// let res = JE.useUtils().execScript4Async(item.events['hidden'], {
// EventOptions,
// }, true)
// console.log(res)
return show;
});
const FuncButtonTypeEnum = Object.freeze({
/**
* 列表Action添加
*/
ACTION_INSERT_BUTTON: "actionAdd",
/**
* 列表Action保存
*/
ACTION_UPDATE_BUTTON: "actionUpdate",
/**
* 列表Action删除
*/
ACTION_REMOVE_BUTTON: "actionRemove",
/**
* 列表Action编辑
*/
ACTION_EDIT_BUTTON: "actionEdit",
});
function doGridActionButtonEvents($func, { button, eventOptions }) {
const { row } = eventOptions;
const { doGridInsert, doGridRemove, doGridEdit, doGridUpdateList } =
$func.action;
switch (button.code) {
case FuncButtonTypeEnum.ACTION_INSERT_BUTTON: // 添加
return doGridInsert({ row });
case FuncButtonTypeEnum.ACTION_REMOVE_BUTTON: // 删除
return doGridRemove({ row });
case FuncButtonTypeEnum.ACTION_UPDATE_BUTTON: // 保存
return doGridUpdateList({ row });
case FuncButtonTypeEnum.ACTION_EDIT_BUTTON: // 编辑
return doGridEdit({ row, actionBtn: true });
}
}
const HDropdown = defineComponent({
setup() {
const visible = ref(false);
return () => {
// 创建菜单项
const menuItems = buttons.map((item, index) =>
h(
JE.useUi().Menu.Item,
{
key: index,
onClick: () => {
console.log($func, item);
if (item.disabled) return;
if (!item.events.click) {
doGridActionButtonEvents($func, {
button: item,
eventOptions: EventOptions,
});
}
item.events["before-click"] &&
JE.useUtils().execScript4Async(
item.events["before-click"],
{
EventOptions,
},
true
);
item.events.click &&
JE.useUtils().execScript4Async(
item.events.click,
{
EventOptions,
},
true
);
item.events["after-click"] &&
JE.useUtils().execScript4Async(
item.events["after-click"],
{
EventOptions,
},
true
);
},
},
item?.text
)
);
// 创建下拉菜单
const overlay = h(JE.useUi().Menu, menuItems);
// 构建下拉组件
return h(
JE.useUi().Dropdown,
{
"v-model:visible": visible.value,
"onUpdate:visible": val => {
visible.value = val;
},
trigger: ["click"],
},
{
default: () =>
h(
"a",
{
class: "ant-dropdown-link",
onClick: e => {
e.preventDefault();
visible.value = !visible.value;
},
},
[
"更多",
h("i", {
class: "fal fa-angle-down",
style: `display:${!visible.value ? "inline-block" : "none"};margin-left:5px`,
}),
h("i", {
class: "fal fa-angle-up",
style: `display:${visible.value ? "inline-block" : "none"};margin-left:5px`,
}),
]
),
overlay: () => overlay,
}
);
};
},
});
// 返回组件
return h(HDropdown);
app(uniapp use plus)某全局脚本通过各种作业票的审批同意触发人脸识别等
Click to expand
const {
$func, // 功能对象
flag = {
signature: true, //拍照签字
position: true, //围栏
gas: false, //气体分析
},
} = EventOptions;
const state = $func.$workflow.state.currentTaskNode;
const isMobile = /Mobile|Android/i.test(navigator.userAgent);
const isTest = location.host === "192.168.0.75";
let faceTargetUser = {};
const level = $func.store.activeBean?.TICKET_LEVEL;
//动土 断路 盲堵
const levelDoSelectList = ["DTZY", "DLZY", "MBCDZY"];
const canDoSelect = levelDoSelectList.includes(level);
//受限空间,临时用电,动火作业
const levelDoGasList = ["SXKJZY", "LSYDZY", "YJDHZY", "EJDHZY", "TJDHZY"];
const params = {
TICKET_INFO_ID:
$func.store.activeBean.TICKET_INFO_ID ||
$func.store.activeBean.TICKET_APPLICATION_ID,
NODE_ID: state && state.id,
NODE_NAME: state && state.name,
};
const { message, ajax } = JE;
const closeWaiting = () => plus && plus.nativeUI.closeWaiting();
const loadScript = (src, callback) => {
try {
const script = document.createElement("script");
script.src = src;
script.onload = callback;
document.head.appendChild(script);
} catch (e) {
alert(e.message);
}
};
const handleError = (error, messageText = "操作失败") => {
closeWaiting();
uni.hideLoading();
message(`${messageText}: ${error.message || "未知错误"}`, "error");
location.reload();
};
const getCapture = async () => {
return new Promise((resolve, reject) => {
const cmr = plus.camera.getCamera();
cmr.captureImage(
url => {
plus.io.resolveLocalFileSystemURL(
url,
entry => {
entry.file(file => {
const reader = new plus.io.FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => resolve(reader.result);
}, reject);
},
reject
);
},
error => {
reject(new Error("取消拍照,返回上级页面"));
setTimeout(() => {
location.reload();
}, 0);
},
{ filename: "_doc/camera/", index: 1 }
);
});
};
const faceRecognition = async (fileKey, userId) => {
try {
// if (!isMobile || isTest) return true;
const { data } = await ajax({
url: "/je/sp/facialRecognition/querySimilarityScore",
method: "post",
headers: { pd: "security-platform-face" },
params: {
USER_FACE_FILE_KEY: fileKey,
JE_RBAC_ACCOUNT_DEPT_ID: userId,
},
});
return data === true;
} catch (error) {
handleError(error, "人脸识别服务错误");
return false;
}
};
const base64ToFile = (base64, fileName) => {
let arr = base64.split(","),
type = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], fileName, { type });
};
const faceRecognize = async userId => {
let name =
faceTargetUser.FACE_ACCOUNT_USER_NAME ||
faceTargetUser.WORK_LIABLE_PERSON_NAME ||
faceTargetUser.text;
JE.confirm({
message: `请识别${name}的人脸进行审批。`,
title: "提示",
okButton: {
text: "确定",
handler: async () => {
try {
await uni.showLoading();
const base64Url = await getCapture();
if (!base64Url) throw new Error("获取图片失败");
const file = base64ToFile(base64Url, "capture.png");
plus &&
plus.nativeUI.showWaiting("人脸识别中,请稍等...", { back: "none" });
const uploadRes = await uni.uploadFile({
file,
header: { authorization: localStorage.getItem("authorization") },
url: `/je/document/file`,
});
const fileKey = JSON.parse(uploadRes.data).data[0].fileKey;
const faceRes = await faceRecognition(fileKey, userId);
closeWaiting();
uni.hideLoading();
if (faceRes) {
message("人脸识别成功", "success");
await JE.callCustomFn("workflowPreProcessing", {
...EventOptions,
userId,
faceTargetUser,
})();
} else {
message("人脸识别未通过", "error");
setTimeout(() => {
location.reload();
}, 500);
}
} catch (error) {
handleError(error);
}
},
},
cancelButton: {
text: "取消",
handler() {
location.reload();
},
},
});
};
const judgeHasPicAndSig = async () => {
try {
const res = await JE.ajax({
method: "POST",
params,
url: "/je/sp/ticket/workflowExtend/app/load",
headers: {
pd: "security-platform-ticket",
},
});
console.log($func.store.activeBean, state, "state");
console.log(res, "judgeHasPicAndSig");
if (res.success) {
if (res.data) {
faceTargetUser = res.data;
if (res.data.FACE_RESULT === "over") {
return "jump";
}
if (res.data.FACE_ACCOUNT_DEPT_ID) {
return res.data.FACE_ACCOUNT_DEPT_ID;
}
}
}
} catch (e) {
console.log(e);
return false;
}
return false;
};
const init = async () => {
loadScript("/static/libs/eruda.min.js", () => eruda.init());
console.log(faceTargetUser, "faceTargetUser");
const doInit = async () => {
const res = await judgeHasPicAndSig();
//之前人脸识别过
if (res === "jump") {
return true;
}
//上一个节点之前选过人
else if (res) {
faceRecognize(res);
}
//动土 断路 盲堵 可以选人进行人脸识别
else if (canDoSelect) {
await JE.showUserSelect({
callback: res => {
if (res && res.rows) {
if (res.rows.length === 0) {
message("请选择人员数据!", "error");
location.reload();
}
console.log(res.rows, "rows");
faceTargetUser = res.rows[0];
faceRecognize(res.rows[0].id);
}
},
customOptions: {
cancelButton: false,
},
});
}
//其他情况直接对当前节点的人进行人脸识别
else {
faceTargetUser = $func.store.activeBean;
// if (!faceTargetUser.WORK_LIABLE_PERSON_NAME) {
const { data } = await ajax({
url: "/je/workflow/currentUserTask/getCirculationHistory",
method: "post",
params: {
beanId: params.TICKET_INFO_ID,
},
});
console.log("data", data);
if (data && data.length > 0) {
data.forEach(item => {
if (item.actualApprover) {
faceTargetUser.WORK_LIABLE_PERSON_NAME = item.actualApprover;
}
});
}
// }
faceRecognize(
$func.store.activeBean?.WORK_LIABLE_PERSON_ACCOUNTDEPT_ID ||
$func.store.activeBean?.APPLICANT_DEPT_ID
);
}
};
if (!levelDoGasList.includes(level)) {
await doInit();
} else {
const gas = await JE.ajax({
url: "/je/sp/ticket/gas/getLatestGasResult",
method: "POST",
headers: {
pd: "security-platform-ticket",
},
params: { TICKET_INFO_ID: params.TICKET_INFO_ID },
});
const confirm = message => {
JE.confirm({
content: message,
okButton: {
text: "继续提交",
handler() {
doInit();
},
},
cancelButton: {
text: "取消",
handler() {
location.reload();
},
},
});
};
if (!gas.success) {
confirm(gas.message);
// doInit();
} else {
const limit = gas.data[0].values.LOWER_LIMIT;
const limit_U = gas.data[0].values.UPPER_LIMIT;
const CONTENT = gas.data[0].values.CONTENT;
if (limit > CONTENT || limit_U < CONTENT) {
confirm("该作业票存在气体分析结果异常情况,请确认是否同意审批?");
} else doInit();
}
}
};
return init;
app(uniapp use plus)通过脚本实现签字拍照
Click to expand
const {
$func, // 功能对象
$form, // 功能表单对象
button, // 按钮对象
flag = {
signature: true, //拍照签字
position: true, //围栏
gas: false, //气体分析
},
faceTargetUser = {},
} = EventOptions;
let { userId = "" } = EventOptions;
let currentUser = {};
let target = faceTargetUser;
const loadScript = (src, callback) => {
try {
const script = document.createElement("script");
script.src = src;
script.onload = callback;
document.head.appendChild(script);
} catch (e) {
alert(e.message);
}
};
const initSignaturePad = () => {
const canvas = document.getElementById("signature-pad");
if (!canvas) return;
signaturePad = new SignaturePad(canvas, {
backgroundColor: "rgba(255, 255, 255, 0)",
penColor: "black",
minWidth: 0.5,
maxWidth: 2.5,
});
function resizeCanvas() {
const ratio = Math.max(devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext("2d").scale(ratio, ratio);
signaturePad.clear();
}
addEventListener("resize", resizeCanvas);
resizeCanvas();
};
const base64ToFile = (base64, fileName) => {
let arr = base64.split(","),
type = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], fileName, { type });
};
const { message, ajax, dialog, uni } = JE;
const { FilePicker } = JE.useUi();
const { h, ref } = JE.useVue();
const judgePosition = async () => {
console.log(
$func,
" $func.store.activeBean",
$func.store.activeBean.APPLICANT_USER_ID,
userId
);
const res = await ajax({
url: "/je/sp/position/work/alarm/judgePosition",
params: {
POSITION_WORK_ALARM_ID:
$func.store.activeBean.POSITION_WORK_ALARM_ID || "",
JE_RBAC_ACCOUNTDEPT_ID:
userId ||
currentUser.deptId ||
$func.store.activeBean.APPLICANT_DEPT_ID,
},
method: "GET",
headers: {
pd: "security-platform-position",
},
});
if (res.success) return;
JE.confirm({
content: res.message,
buttons: [
{
text: "取消",
handler() {
location.reload();
},
},
],
});
throw new Error(res.message);
};
const rotate = base64 => {
const img = new Image();
return new Promise((res, rej) => {
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = img.height;
canvas.height = img.width;
const ctx = canvas.getContext("2d");
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.translate(0, img.width);
ctx.rotate((-90 * Math.PI) / 180);
ctx.drawImage(img, 0, 0, img.width, img.height);
const newBase64Str = canvas.toDataURL("image/png", 1.0);
res(newBase64Str);
};
img.src = base64;
});
};
const analysisGas = () => {
const gasTime = $form.getValue("GAS_TIME");
if (!gasTime || gasTime == "") {
JE.confirm({
content: "30min内未进行气体分析",
buttons: [
{
text: "取消",
handler() {
location.reload();
},
},
],
});
throw new Error("30min内未进行气体分析");
// return false;
}
const date = new Date(gasTime).getTime();
const nowData = new Date().getTime();
if (nowData - date > 30 * 60 * 1000) {
JE.confirm({
content: "30min内未进行气体分析",
buttons: [
{
text: "取消",
handler() {
location.reload();
},
},
],
});
throw new Error("30min内未进行气体分析");
// return false;
}
};
let signaturePad = null;
const includeSuffix = ["jpeg", "jpg", "png"];
const fileValue = ref("");
const state = $func.$workflow.state.currentTaskNode;
const params = {
tableCode: "TICKET_INFO_WORKFLOW_EXTEND",
TICKET_INFO_ID:
$func.store.activeBean.TICKET_INFO_ID ||
$func.store.activeBean.TICKET_APPLICATION_ID,
NODE_ID: state && state.id,
NODE_NAME: state && state.name,
SIGN: null, //签字信息
PICTURE: null, //照片信息
FACE_RESULT: "over",
FACE_ACCOUNT_DEPT_ID: userId,
FACE_ACCOUNT_USER_NAME:
target.FACE_ACCOUNT_USER_NAME ||
target.WORK_LIABLE_PERSON_NAME ||
target.text,
};
const styles = {
header: "font-size:1.2em;font-weight:bolder;",
hidden: "display:none",
show: "display:block",
flex: ` display:flex;gap:20px;
justify-content:space-between;
width:90%; `,
primary: ` background-color: #3265f5;
color: white;
min-width:100px;
padding: 10px 20px;
border: none;
border-radius: 20px;
font-size: 1em;`,
container: `
display:flex;
flex-direction:column;
gap:10px;
height:100%;
overflow-y:auto;
overflow-x:hidden;
align-items:center;`,
};
const showPictureWindow = async () => {
await JE.window({
height: "100vh",
maximized: true,
showHeader: false,
contentStyle: {
padding: 0,
},
title: "现场照片",
content: () => {
return h("div", {}, [
h(FilePicker, {
type: "image",
includeSuffix,
multiple: true,
maxCount: 10,
value: fileValue.value,
includeMessage: `请上传支持的文件格式:${includeSuffix.join("、")}`,
maxSize: 20,
"onUpdate:value": val => {
fileValue.value = val;
console.log(fileValue.value);
params.PICTURE = fileValue.value;
},
}),
]);
},
buttons: [
{
text: "保存",
type: "primary",
closable: false,
handler({ $modal }) {
$modal.close();
// save($modal);
},
},
{
text: "取消",
closable: false,
handler({ $modal }) {
$modal.close();
},
},
],
});
};
const save = $modal => {
try {
if (!params.PICTURE) {
dialog({
title: "注意",
content: "请上传现场照片!",
});
return;
}
if (signaturePad.isEmpty()) {
dialog({
title: "注意",
content: "请签字",
});
return;
}
const url = signaturePad.toDataURL("image/png");
rotate(url).then(resUrl => {
const file = base64ToFile(resUrl, "sign.png");
uni.uploadFile({
file,
header: {
authorization: localStorage.getItem("authorization"),
},
url: `/je/document/file`,
success: async res => {
// message("签名上传成功");
const signData = JSON.parse(res.data);
if (signData && signData.data && signData.data.length > 0)
params.SIGN = JSON.stringify(signData.data);
console.log(params, "params");
plus && plus.nativeUI.showWaiting("保存中...");
ajax({
url: "/je/sp/ticket/workflowExtend/doSave",
params,
method: "POST",
headers: {
pd: "security-platform-ticket",
},
}).then(() => {
plus && plus.nativeUI.closeWaiting();
message("保存成功", "success");
$modal.close();
});
},
});
});
} catch (e) {
throw new Error(e.message);
}
};
const openWindow = async () => {
await JE.window({
height: "100vh",
maximized: true,
showHeader: false,
contentStyle: {
padding: 0,
},
content: () => {
const canvas = h(
"canvas",
{
id: "signature-pad",
style: `
width:98%;
height:100%;
flex:1;
margin:10px auto 0;
z-index:2;
background-color: rgba(255,255,255,0.1);`,
},
[]
);
const tipText = h(
"div",
{
style: `font-size: 3rem;
opacity:0.1;
transform-origin: center;
position: absolute;
white-space: nowrap;
z-index:1;
top: 50%;
left: 50%;
touch-action: none;
transform: translate(-50%, -50%) rotate(90deg);`,
},
"签字区"
);
return h(
"div",
{
style: `${styles.container}`,
},
[canvas, tipText]
);
},
buttons: [
{
text: "现场照片",
closable: false,
type: "warning",
handler({ $modal }) {
showPictureWindow();
},
},
{
text: "清空",
closable: false,
type: "warning",
handler({ $modal }) {
signaturePad && signaturePad.clear();
},
},
{
text: "回退",
closable: false,
type: "success",
handler({ $modal }) {
const data = signaturePad.toData();
if (data) {
data.pop(); // remove the last dot or line
signaturePad.fromData(data);
}
},
},
{
text: "提交",
type: "primary",
closable: false,
handler({ $modal }) {
save($modal);
},
},
{
text: "取消",
closable: false,
handler({ $modal }) {
location.reload();
},
},
],
});
};
const initButtonsStyle = () => {
const buttons = document.querySelectorAll(
".je-modal-window__buttons .button"
);
if (!buttons) return;
buttons.forEach(item => {
item.style.marginBottom = "10px";
item.style.transform = "rotate(90deg)";
item.style.transformOrigin = "center";
});
};
const init = async () => {
// if (!/Mobile|Android/i.test(navigator.userAgent)) return true;
try {
loadScript("/static/libs/eruda.min.js", () => eruda.init());
const res = await JE.ajax({
url: "/je/rbac/cloud/account/currentUser",
params,
method: "GET",
});
if (res.success) {
currentUser = res.data;
if (!userId) {
userId = currentUser.deptId;
}
if (!target.FACE_ACCOUNT_USER_NAME) {
target.FACE_ACCOUNT_USER_NAME = currentUser.name;
}
}
flag.position && (await judgePosition());
flag.gas && analysisGas();
if (flag.signature) {
try {
await openWindow();
initButtonsStyle();
loadScript(`/static/libs/signature_pad.umd.min.js`, () => {
initSignaturePad();
});
} catch (e) {
plus.nativeUI.closeWaiting();
throw e;
}
}
return true;
} catch (e) {
return false;
}
};
return init;