简介这份资源为无人机农业应用App的前端项目压缩包面向无人机农业开发者、算法研究人员及对精准农业感兴趣的技术爱好者。项目代码基于Vue.js组件化开发围绕无人机在精准农业、病虫害监测、农田测绘中的实际应用实现了多光谱图像展示、航线规划、遥控指令交互等核心页面适合用于学习前端如何衔接后端算法与传感器数据。资源共112个文件包括26个vue组件、29个js脚本、35个png图标与界面素材、8个html入口页面以及少量styl样式、json配置和md说明文件整体大小2.59MB目录结构简洁便于按功能模块定位与修改。目前已有190人学习浏览。通过阅读源码可掌握农业无人机监控界面的模块划分、数据请求封装及地图可视化思路尤其能学习如何将算法输出的农田分析结果转化为可交互的仪表盘与指令面板同时目录中保留的HTML页面可直接预览便于快速理解各页面路由与组件复用关系适合在此基础上二次开发构建自己的农业巡检或植保作业平台。1. 无人机农业应用app.zip不是飞行控制端而是农业作业登记前端第一次接手农业植保信息化项目时我被一个现实问题卡住了飞机在天上飞得再稳地面端的作业数据却是一团乱。飞手用纸质表记录地块、用药量、飞行架次回来再手工录入系统漏填、错填、格式不统一成了家常便饭。这份无人机农业应用app.zip带来的是一套可以直接在手机和浏览器里跑起来的作业登记前端原型包含土地登记、飞手信息、作业工单和城市选择等页面解决的是“无人机农业应用如何从作业现场走进信息系统”的第一公里。适合正在做农业SaaS、植保服务管理后台或无人机相关课程设计的开发者先把业务流程跑通再补算法和硬件也不迟。2. 农业无人机作业的三条主线航测、巡检与变量喷洒农业无人机的作业场景看起来都是“飞一遍”实际数据链路差异很大。有人做测绘要的是厘米级定位和正射影像有人做巡检要的是高清图像的稳定采集还有人做变量喷洒核心是结合处方图实时调整流量。三种模式下无人机算法和地面端表单的侧重点完全不同。2.1 农田测绘高度、重叠率与GSD怎么配农田测绘的产出物通常是正射影像DOM和地形图用的载荷以多光谱相机或普通可见光相机为主。飞行高度直接决定地面采样距离GSD而重叠率决定后期影像拼接能否成功。我见过不少新手把飞行高度拉到 150 米以上去“这样一趟飞得大”结果拼接出来的影像出现大量空洞原因是旁向重叠率掉到了 60% 以下。业内每天都在用的保守参数是航向重叠率控制在 80% 左右旁向重叠率至少 70%风速大于 5 级就停止航测作业。航线一般平行于田块长边速度控制在 5-7 米每秒直飞段之间用弓字形衔接减少无效转弯。参数经验取值影响飞行高度90-120 米越高GSD越大影像越粗航向重叠率75%-85%低于70%拼接易出空洞旁向重叠率60%-75%航线衔接的关键飞行速度5-7 米/秒过快导致影像模糊RTK定位开启航测必须厘米级定位GSD 不是一个拍脑袋给的数它可以先用公式估一遍再决定是否起飞def estimate_gsd(focal_mm, sensor_width_mm, image_width_px, alt_m): # 焦段、传感器宽度、像素宽度、飞行高度 ground_width_m sensor_width_mm * alt_m / focal_mm gsd_cm ground_width_m * 100 / image_width_px return round(gsd_cm, 2) # 以 DJI 御2行业版为例 print(estimate_gsd(10.3, 12.8, 5472, 100)) # 输出约2.27cm/px其中 focal_mm 是镜头焦距sensor_width_mm 是 CMOS 宽度image_width_px 是单张影像像素宽度alt_m 是相对起飞点的飞行高度。把这个公式写进航线规划脚本里能在现场快速判断“这个高度拍的图能不能满足出 DOM 的质量要求”。我一般会再加一个判断当 GSD 超过 5cm/px直接把飞行高度降 20 米重新算一遍而不是带着侥幸起飞。2.2 病虫害巡检图像采集质量比识别模型更关键做病虫害识别时很多人的第一反应是“上模型”实际情况往往是模型没太大问题图像采集质量拖了后腿。无人机飞到 80 米高空拍到的叶片只有指甲盖大小再强的卷积神经网络也认不出病斑纹理。所以巡检作业要先把采集参数定死飞行高度 30-50 米云台角度垂直向下即 90°相机快门优先拍摄间隔按 1-2 秒一张。另一个容易忽略的点是黑匣子式的训练集与现场差异。用公开数据集训练出的识别模型在换地区、换光照后往往识别率大幅下降。常见做法是先在目标地块采集 200-300 张病害图像用随机翻转、色彩抖动、高斯噪声做数据增强把增强后的样本混入原训练集微调模型。import albumentations as A transform A.Compose([ A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.3), A.HueSaturationValue(hue_shift_limit10, sat_shift_limit20), A.GaussNoise(var_limit(10.0, 30.0)), ]) # 对每张现场采集图做3次增强 aug_images [] for img in field_samples: for seed in [1, 2, 3]: random.seed(seed) aug transform(imageimg) aug_images.append(aug[image])这里每次强增强都固定随机种子是为了保证增强过程可复现。遇到效果不佳时能回滚到某个确定版本而不是玄学调参。增强后的图像在送入目标检测网络前还要统一缩放到模型输入尺寸例如 640×640避免因为分辨率不一致导致识别结果跳动。2.3 变量喷洒与轨迹规划A*和Dijkstra怎么选农业无人机喷洒作业最依赖无人机算法的地方有两个一是精准的厘米级定位二是全覆盖路径规划。RTK差分定位可以把误差控制在 2-3 厘米内这是变量喷洒处方图能不能落地的前提。定位只有米级精度时喷幅边缘重喷和漏喷都会明显。路径规划方面教科书喜欢比较 A* 和 Dijkstra。Dijkstra 是无启发式搜索保证找到最短路径但开销大A* 加了启发函数在农田场景下往往能快数倍收敛。田块内部通常没有复杂障碍物真正容易踩坑的是边界不规则和电线杆。import heapq def astar(grid, start, goal): # grid: 二维栅格1为障碍返回路径点列表 h lambda a, b: abs(a[0] - b[0]) abs(a[1] - b[1]) open_set [(h(start, goal), start)] came_from, cost_so_far {}, {start: 0} while open_set: _, current heapq.heappop(open_set) if current goal: break for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: nxt (current[0] dx, current[1] dy) if 0 nxt[0] len(grid) and 0 nxt[1] len(grid[0]) and grid[nxt[0]][nxt[1]] 0: new_cost cost_so_far[current] 1 if nxt not in cost_so_far or new_cost cost_so_far[nxt]: cost_so_far[nxt] new_cost came_from[nxt] current priority new_cost h(nxt, goal) heapq.heappush(open_set, (priority, nxt)) return reconstruct_path(came_from, start, goal)启发函数用的是曼哈顿距离适合网格移动模型。start 和 goal 分别为起飞点和目标点grid 中的 1 代表电塔、树林等障碍物。这个片段放在地面站的规划模块里可以作为核心搜索函数实际工程中还会把无人机转角约束加进 cost 里防止规划出急转弯航线。三个作业模式下地面端App需要沉淀的数据其实很固定地块边界、面积、作物类型、飞手、作业时间、用药量。接下来的页面拆解就能看到这些字段是怎么落在表单里的。3. 拆解核心页面cerland、cername、cerjob与citySelect的数据流转资源包里的四个 HTML 页面是整套业务主链路先选城市确定作业区域再登记土地然后登记飞手和资质最后派发作业工单。数据在这四个页面里是一层层往上叠的。3.1 cerland.html地块登记与坐标边界cerland 页面承担土地信息采集核心是三个字段地块名称、面积、边界坐标。面积直接用 number 输入框边界坐标放在隐藏域里由地图组件在用户画圈后写入。form idlandForm div classform-item label forlandName地块名称/label input typetext idlandName nameland_name placeholder如东区三号田 required /div div classform-item label forlandArea面积亩/label input typenumber idlandArea nameland_area min0 step0.1 required input typehidden idlandBoundary nameland_boundary /div div classform-item label forcropSelect作物类型/label select idcropSelect namecrop_type option valuerice水稻/option option valuewheat小麦/option option valuemaize玉米/option /select /div button typesubmit保存地块/button /formland_boundary 隐藏域保存 GeoJSON 格式的边界字符串例如{type:Polygon,coordinates:[...]}。这样提交给后端后后端不需要再解析地图组件内部数据直接统一按 GeoJSON 存储兼容性最好。我一般会在表单提交前做一道数据完整性检查边界点少于 4 个或多边形自交都直接拦截。因为农业地块经常是不规则形状地图组件手动描点很容易画出“扭麻花”式多边形这种边界数据进到航线规划模块里生成的喷洒路径会乱成一团。3.2 cername与cerjob飞手资质与作业工单的两级联动cername 页面收集飞手信息包括姓名、身份证号、无人机驾驶证编号和联系电话。cerjob 页面则是作业工单引用前面登记的飞手和地块把两者绑定在一起。这两个页面的表单逻辑比较直接关键在于下拉框的数据来源。const jobForm document.getElementById(jobForm); const pilotSelect document.getElementById(pilotSelect); const landSelect document.getElementById(landSelect); // 从本地存储读取上一页已登记数据 const pilots JSON.parse(localStorage.getItem(cer_pilots) || []); const lands JSON.parse(localStorage.getItem(cer_lands) || []); pilots.forEach(p { const opt document.createElement(option); opt.value p.id; opt.textContent ${p.name}${p.cert_no}; pilotSelect.appendChild(opt); });这里的思路是cername 保存的飞手列表和 cerland 保存的地块列表在 cerjob 页加载时通过 localStorage 同步到下拉框。纯前端Demo阶段不需要后端数据库所有数据都落本地方便产品经理快速验证业务流程。等后端接口就绪后把 localStorage.getItem 换成 fetch(/api/pilots) 即可改动面很小。3.3 citySelect.html省市区联动背后的数据约定citySelect 页面是整套应用的入口先选省再选市、区县。这个页面单独拆出来的原因很直接农业作业服务往往按行政区划组织后续的地块归属、飞手调度都以区县为最小单位。页面拿到 erritory 代码后写入本地存储后续所有页面都能读取。select idprovinceSelect nameprovince/select select idcitySelect namecity/select select iddistrictSelect namedistrict/select省市区三级数据结构化地存在一个cityData.json里每级节点包含code和name。三级联动的常见做法是监听上级 select 的 change 事件重新渲染下级选项并且每次切换上级后下级重置为“请选择”。这里最需要注意的是编码规范相同行政区划在不同页面里必须使用同一套编码。我之前见过一个项目地块归属用身份证前6位工单归属用自增ID结果统计报表时两个字段对不上返工了三天才统一口径。四个页面串起来的完整数据链路可以概括为citySelect 确定区位 → cerland 登记地块 → cername 登记飞手 → cerjob 派发工单。先把这条链路跑通后面接协议解析、无人机实时状态上报都只是在这套前端骨架上加模块。4. 本地跑起来工程配置怎么理解、服务怎么启动很多同学解压这个包后直接双击 HTML 文件发现页面能开、下拉框不联动就开始怀疑资源有问题。实际上问题出在模块化加载cityData.json 在 file:// 协议下无法被 fetch 读取必须起一个本地 HTTP 服务。4.1 .babelrc和工程配置文件是干什么的资源包里有 .babelrc、.eslintignore、.gitignore 等工程配置文件但找不到 package.json。这说明它不是一套完整 npm 工程而是把配置预先埋好方便后续升级成正式前端项目时直接复用。.babelrc 的作用是把 ES6 语法转成兼容性更好的 ES5。{ presets: [babel/preset-env], plugins: [babel/plugin-transform-runtime] }preset-env 按目标浏览器自动确定转译规则runtime 插件则避免辅助函数重复生成。这个配置在后续用 Vue 或 React 重写页面时依然能用不需要从零定。.eslintrc 则负责约定代码规范例如变量必须用 const/let、禁止 console.log 出现在生产分支等。{ env: { browser: true, es2021: true }, rules: { no-console: warn, quotes: [error, double] } }把这两份配置提前放进资源的意图很明显当前是静态 Demo下一步迁移到构建工程时不用再配编译和规范相关的东西。理解了这点你就知道这个包不是让你“装完就能部署上线”而是给后续开发铺好了第一层地板。4.2 用 Python 起一个静态服务三分钟跑起来本机只要有 Python 3就不需要额外找包直接用内置模块起服务对照资源包目录是被远程拉取还是本地备份的简化方案。cd 无人机农业应用app python3 -m http.server 8080浏览器访问 http://localhost:8080/cerland.html 即可看到页面。注意-m http.server的参数是端口号按 CtrlC 停止。第一次跑的时候如果发现页面里地图组件空白大概率是地图插件还需要加载在线 SDK和本地服务无关。如果机器上装了 Node.js我一般优先用 npx 起服务因为它会直接打开默认浏览器省一步操作npx serve -l 8080这里-l是 listen 的缩写8080 是端口。两种方式效果一样选顺手的那种就好。4.3 数据接口预留长什么样整个包全是静态页面没有后端代码但表单的设计已经预留了接口位置。后续对接真实后端时在表单提交事件里把 fetch 调用补上即可document.getElementById(jobForm).addEventListener(submit, async (e) { e.preventDefault(); const formData new FormData(e.target); const payload Object.fromEntries(formData.entries()); // POST到作业工单接口 const resp await fetch(/api/jobs, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(payload) }); const result await resp.json(); localStorage.setItem(last_job_id, result.id); });跨域时可以在请求里带上 credentials 参数或者让后端开发统一开启 CORS。纯前端阶段我习惯把提交按钮的真实请求注释掉先只写 localStorage等后端接口文档给到再解封能有效避免前后端一起联调时互相甩锅。5. 避坑指南无人机前端开发最常见的五处翻车点这套资源本身不复杂但无论是做课程设计还是给客户做演示都会在连续点击中遇到几个固定翻车点。我把它们按现象、原因、解决三步写出来你遇到同款问题时直接照做。5.1 表单校验失效空数据照样提交现象点击提交按钮所有字段都留空表单却提示“提交成功”。原因页面只写了 required 属性但 form 的 submit 事件里调用了 e.preventDefault()把浏览器默认校验逻辑一并拦截了。解决在 submit 处理函数开头手动触发表单合法性检查const form document.getElementById(landForm); form.addEventListener(submit, (e) { if (!form.checkValidity()) { e.preventDefault(); form.reportValidity(); return; } // 校验通过继续业务逻辑 });checkValidity 会按 HTML5 规则检查全部 required 字段reportValidity 负责弹出提示气泡。从那以后我把“校验必须显式调用”写成了自己的前端铁律。5.2 城市选择器联动失效现象切换省份后市级下拉框还是空列表。原因省市区数据通过 fetch 加载城市数据数组为空时没有做空态处理change 事件里找不到对应子级就直接返回。解决切换上级后用find查找当前选中项再重新渲染子级。同时加一个兜底判断子级列表为空时渲染“无数据”占位。5.3 坐标数据死活存不进去现象地图里能画圈保存后刷新页面地块边界就消失了。原因页面刷新后内存中的地图实例被销毁边界坐标只存在于 JS 变量里没有写入 localStorage 或后端。解决在地图的“绘图结束”事件里直接把几何数据写入 hidden input并在页面加载后读取该字段还原地图。记住一点页面所有状态在刷新后都会丢只有保存进持久化存储的数据才会留下。5.4 不同页面拿不到同一条数据现象cerland 页填的地块信息在 cerjob 页下拉框里看不到。原因页面之间用变量传值刷新后变量丢失或者 localStorage 的 key 拼写不一致。解决固定使用常量字符串管理 key建议全小写下划线例如cer_lands、cer_pilots。写一个简单工具函数const store { get: (key) JSON.parse(localStorage.getItem(key) || []), set: (key, value) localStorage.setItem(key, JSON.stringify(value)) };统一入口后再也不会出现某个页面读不到数据的问题。5.5 页面样式突然乱掉现象跑着跑着 CSS 加载失败页面变成纯文字堆叠。原因reset.css 使用了相对路径base 标签或路由参数变化导致路径解析错误。解决把所有静态资源引用改成相对当前页面文件的标准写法并确认 HTML 模板头部没有多余的前缀干扰路径解析。6. 进阶玩法给静态原型加一个可提交的最小数据闭环资源包现在是纯前端演示我们再加一段脚本把表单数据先暂存本地、再模拟提交后端形成最小闭环。这一步做完你就可以拿这套页面去做真实的业务演示不用再到处解释“数据暂存内存”。class JobService { constructor() { this.KEY cer_jobs; } list() { return JSON.parse(localStorage.getItem(this.KEY) || []); } create(job) { const jobs this.list(); job.id J${Date.now()}; job.status pending; jobs.push(job); localStorage.setItem(this.KEY, JSON.stringify(jobs)); return job; } } const service new JobService(); document.getElementById(jobForm).addEventListener(submit, (e) { e.preventDefault(); const fd new FormData(e.target); const job Object.fromEntries(fd.entries()); service.create(job); renderJobList(); }); function renderJobList() { const container document.getElementById(jobList); container.innerHTML service.list() .map(j p${j.id} - ${j.pilot_name} - ${j.land_name} - ${j.status}/p) .join(); }这段把“表单提交 → 落本地 → 回显列表”三个动作串成一个闭环。JobService 里的 KEY 统一管理存储字段create 方法生成以时间戳为基础的工单号避免主键冲突。renderJobList 每次提交后重新渲染列表演示时可以直观看到工单一条条生成。核心技巧在最后一行数据只要能被持久化、被回读、被展示前端原型的业务完整度就到位了。后端接口到位后只需要改 create 方法把 localStorage.setItem 换成 POST 请求即可业务流程和数据结构都不用动。这套做业务的原型开发方式比我早期“边写接口边画页面”的效率高出一大截。这份包的价值就在于此它不是完整的飞行控制软件但把农业作业地面端最关键的登记流程搭成了骨架你只需要往里填自己的业务字段。项目推进中我最深的教训是“前端先闭环、后端后对接”从那以后每次做农业信息化项目我都强制自己先把这套本地数据循环跑通再碰接口。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?