ArcGis系列-java调用GP分析

news/2024/10/17 19:22:57/

1,实现流程

  1. 创建GPServer,使用ArcgisPro添加GP工具运行,然后使用共享web服务发布运行成功的GP任务
  2. 根据发布成功的GPServer发布地址,解析出GP服务的输入参数和输出参数
  3. 前端输入gp服务需要的参数,发送给后端来异步提交
  4. 后端提交后创建轮询任务等待执行结果
  5. 收到执行结果后解析,根据输出结果类型(表格、矢量、栅格)分别处理和保存
  6. 后端将需要添加样式的矢量或栅格数据重新发布为MapServer
  7. 前端展示表格数据,渲染带样式的GP结果的MapServer

2,GPServer的发布

发布gp工具,使用arcgis pro登录要发布服务的门户
在这里插入图片描述
在gispro中点击share——share web tool,然后选择刚刚成果运行的分析记录,点击ok
在这里插入图片描述
发布gp分析服务
在这里插入图片描述

2,解析GP服务

上一步发布成功的话,使用rest服务在web页面上应该能找到刚发布的服务,地址类似这样https://aaa.server.com:6443/arcgis/rest/services
在这里插入图片描述
在这里插入图片描述
前端使用该gp的url就能解析出输入参数和输出参数,并确定每个参数的类型;

  • GPRasterDataLayer栅格数据
  • GPFeatureRecordSetLayer矢量图层(属性和坐标信息)
  • GPRecordSet 属性表格

如果输入参数类型是矢量应该支持geojson、shp、数据库的空间表
如果输出参数是矢量和栅格,可以在提交GP分析时一并把渲染样式一起提交到后端
一个gp分析可能会返回多个output,每个output都可能是矢量和栅格,也就是都可以作为图层支持添加样式
一般前后端访问rest接口都只需要解析json数据,不需要html内容,所以请求url都追加 “?f=json”

3,提交GP分析

考虑某些GP分析可能很耗时,如果分析数据巨大可能一天,需要注意两点:
1,矢量数据尽量不要使用geojson或具体文件,而是使用已经发布到arcgis的发布url,可以参考空间表发布到arcgis
2,提交请求使用post异步提交,轮询执行结果(需要考虑终止轮询)

因为链路太长,周期长,所以建议每一步都保存执行进度,提交GP分析的代码示例如下:

	  /*** 提交GP分析** @param param*/public void submitJob(GpParam param) {//重新执行时清理正在运行的轮询cleanOldGP(param.getGpId());param.setStatus(1);param.setBeginTime(LocalDateTime.now());mongoTemplate.insert(param, "app_gp_param");//后续操作后台运行taskExecutorCompletableFuture.runAsync(() -> submitJobAsync(param), taskExecutor);}/*** 异步处理gp任务** @param param*/@Async("taskExecutor")public void submitJobAsync(GpParam param) {String gpId = param.getGpId();Map<String, Object> paramMap = new HashMap<>();//指定返回格式为jsonparamMap.put("f", "json");//指定输出坐标系paramMap.put("env:outSR", "4490");log.info("开始解析gp参数{}", gpId);for (String key : param.getGpParams().keySet()) {//区分是数据库空间表还是基本数据类型,数据库空间表这里我用@符号分割了数据库id和表名String val = param.getGpParams().get(key) + "";if (val.indexOf("@") > 0) {//替换为数据库的发布地址作为数据源String[] tableInfo = val.split("@");TableInfoDO tableInfoDO = mongoTemplate.findOne(new Query(Criteria.where("dbId").is(tableInfo[0]).and("name").is(tableInfo[1])), TableInfoDO.class);if (tableInfoDO == null) {log.error("空间表不存在,{}", val);updateStatus(gpId, 3, "空间表不存在," + val);return;}String a = "";if (StrUtil.isEmpty(tableInfoDO.getGpUrl())) {log.warn("数据库表未发布arcgis:{}", val);Update update = new Update();update.set("startTime", LocalDateTime.now());//单表发布到arcgis的代码参考我ArcGis系列下的另一篇文章。。。。。。。a = tablePubUrl;if (a.contains("error")) {log.error("空间表发布到arcgis失败,url:{},失败信息:{}", tablePubUrl, a);updateStatus(gpId, 3, "空间表发布到arcgis失败," + tablePubUrl);return;}//更新表发布地址update.set("gpUrl", tablePubUrl);update.set("endTime", LocalDateTime.now());mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(tableInfoDO.getId())), update, "db_database_table");} else {a = tableInfoDO.getGpUrl();}//以url形式提交gp参数 http://aaa.server.com/server/rest/services/Hosted/CONTOUR1/FeatureServer/0JSONObject object = new JSONObject();object.put("url", a);paramMap.put(key, JSONObject.toJSONString(object));} else {paramMap.put(key, val);}}String url = param.getGpUrl() + "/submitJob?f=json";log.info("提交分析url:{},参数:{}", url, paramMap.toString());String groupStr = HttpRequest.post(url).form(paramMap).contentType("application/x-www-form-urlencoded").timeout(60000).execute().body();//{"jobId":"jf10c44e3286f47f989abbe1a99f0c3ba","jobStatus":"esriJobSubmitted"}log.info("提交分析返回:{}", groupStr);if (groupStr.contains("error")) {//保存GP分析数据Update update = new Update();update.set("status", 3);update.set("error", groupStr);mongoTemplate.updateFirst(new Query(Criteria.where("gpId").is(gpId)), update, GpParam.class);return;}JSONObject jsonObject = JSON.parseObject(groupStr);String jobId = jsonObject.get("jobId") + "";//保存GP分析数据param.setJobId(jobId);Update update = new Update();update.set("jobId", jobId);mongoTemplate.updateFirst(new Query(Criteria.where("gpId").is(gpId)), update, GpParam.class);// 创建轮询任务String pageId = param.getPageId();log.info("创建轮询任务,每5s查询一次GP分析结果");ScheduledFuture<?> future = executor.scheduleAtFixedRate(() -> {if (taskStateMap.get(gpId) != null && taskStateMap.get(gpId)) {return;}taskStateMap.put(gpId, true);// 查询GP分析结果boolean res = executeGPTask(url, jobId, pageId, gpId);// 判断是否需要终止轮询任务if (res) {// 取消轮询任务taskStateMap.remove(gpId);if (taskMap.get(gpId) != null) {taskMap.get(gpId).cancel(false);//从任务列表中删除该用户的轮询任务taskMap.remove(gpId);}} else {taskStateMap.put(gpId, false);}}, 0, 5, TimeUnit.SECONDS); // 延迟0秒开始执行,并每隔5秒执行一次// 将任务添加到任务列表中taskMap.put(gpId, future);}

4,轮询GP分析结果

很多刚接触的同事可能在这一步一直获取提交失败的错误,建议先在gp分析的web界面下方点击submit调试,确保页面上能正常拿到数据再来编码
在这里插入图片描述
在这里插入图片描述

  • 执行状态有已提交,执行中,执行失败,执行成功,
  • 后两种状态获取到就终止轮询,并修改gp的执行状态,
  • 执行成功时解析分析结果并处理(加样式发布为图层)
  • 因为矢量和表格数据都是json数据,所以都保存为json,栅格是tif的单独保存
/*** 轮询gp执行状态** @param url    http://127.0.0.1:6080/arcgis/rest/services/test/Model5/GPServer/landconflicts/submitJob?f=json* @param jobId* @param pageId* @param gpId* @return*/private boolean executeGPTask(String url, String jobId, String pageId, String gpId) {//http://127.0.0.1:6080/arcgis/rest/services/test/Model5/GPServer/landconflicts/jobs/j7d80b44b27834e428a5da531fdfcb1c9?f=jsonurl = url.replace("submitJob", "jobs/" + jobId);String res = HttpUtil.get(url);if (res.contains("error")) {//保存异常信息updateStatus(gpId, 3, res);//异常信息,中断轮询return true;}//执行结束,解析结果JSONObject jsonObject = JSON.parseObject(res);String state = jsonObject.get("jobStatus") + "";//已提交 | 执行中if ("esriJobSubmitted".equals(state) || "esriJobExecuting".equals(state)) {return false;} else if ("esriJobSucceeded".equals(state)) {//执行成功JSONObject jsonObj = jsonObject.getJSONObject("results");//创建目录new File(layerParamLocation + gpId + "\\json").mkdirs();new File(layerParamLocation + gpId + "\\tif").mkdirs();for (String outputName : jsonObj.keySet()) {try {GpResult gpResult = new GpResult();gpResult.setOutputName(outputName);gpResult.setJobId(jobId);gpResult.setGpId(gpId);gpResult.setPageId(pageId);gpResult.setEndTime(LocalDateTime.now());//查询outputName输出结果String newUrl = url.replace("?f=json", "/results/" + outputName + "?f=pjson");//保存分析结果到json文件 gpResult.setGpResult(HttpUtil.get(newUrl));log.info("查询output结果,{}", newUrl);gpResult.setGpResult(newUrl);String resStr = getFeatureJson(newUrl);JSONObject resJson = JSON.parseObject(resStr);if (resJson.get("error") != null) {//保存异常信息log.error("获取执行结果异常,{}", newUrl);updateStatus(gpId, 3, "获取执行结果异常," + newUrl);return true;}String resFilePath = "";//按照output类型分别存储矢量json和栅格urlif ("GPRasterDataLayer".equals(resJson.getString("dataType"))) {//栅格数据下载tif到本地 https:/127.0.0.1:6443/arcgis/rest/directories/arcgisjobs/gp/demcreate04_gpserver/j6b911eade85d4ed88748abeef0c3a0ac/scratch/dem2.tif//表格GPRecordSet和矢量GPFeatureRecordSetLayerresFilePath = layerParamLocation + gpId + "\\tif\\" + outputName + ".tif";String tifUrl = resJson.getJSONObject("value").getString("url");FileUtils.downloadImage2(tifUrl, resFilePath);//gpResult.setGpResult(tifUrl);} else {//表格GPRecordSet和矢量GPFeatureRecordSetLayerresFilePath = layerParamLocation + gpId + "\\json\\" + outputName + ".json";FileUtils.saveStringToFile(resJson.getJSONObject("value").toString(), resFilePath);}mongoTemplate.remove(new Query(Criteria.where("gpId").is(gpId).and("outputName").is(outputName)), GpResult.class);mongoTemplate.insert(gpResult, "app_gp_result");log.info("保存gp结果{}完成,{}", outputName, resFilePath);} catch (Exception e) {//保存异常信息log.error("保存执行结果异常:{}", e.toString());updateStatus(gpId, 3, "保存执行结果异常," + url);return true;}}//将执行结果追加样式并发布publishGPResult(gpId);return true;} else if ("esriJobFailed".equals(state)) {//执行失败Update update = new Update();update.set("status", 3);update.set("endTime", LocalDateTime.now());update.set("error", res);mongoTemplate.updateFirst(new Query(Criteria.where("jobId").is(jobId)), update, GpParam.class);return true;} else {log.error("gp执行未知状态:{},完整报文:{}", state, res);return true;}}/*** 获取gp分析结果的矢量数据,主要针对结果数据量很大或网络波动导致的读取超时* @param url* @return*/public String getFeatureJson(String url) {String result = HttpUtil.createGet(url).setConnectionTimeout(30000).setReadTimeout(30000).execute().body();return result;}

5,将GP分析结果发布为Map

将gp结果发布到arcgis同样需要调用python脚本,构建本地项目创建草稿并上传发布

  • 发布图层需要的矢量和栅格数据从上一步保存的json和tif获取,需要的样式文件从前端提交gp时的保存的路径取
  • 发布gp结果的详细代码放在同系列的另一篇文章中( ̄▽ ̄)*
/*** 给GP分析结果添加样式并重新发布** @param gpId*/private void publishGPResult(String gpId) {Update update = new Update();GpParam gpParam = mongoTemplate.findOne(new Query(Criteria.where("gpId").is(gpId)), GpParam.class);// 图层参数临时目录String layerParamDir = layerParamLocation + gpId;log.info("开始遍历发布gp结果:{}", layerParamDir);String url = null;try {url = pythonExecutor.publishLayerToArcgis(layerParamDir, getBaseUrl(gpParam.getGpUrl()), "lzwpro", "xxxxxx", gpId);} catch (Exception e) {log.error("发布gp结果异常:{},{}", gpId, e.toString());updateStatus(gpId, 3, e.toString());return;}update.set("status", 2);update.set("publishUrl", url);//更新GP任务状态update.set("endTime", LocalDateTime.now());mongoTemplate.updateFirst(new Query(Criteria.where("gpId").is(gpId)), update, GpParam.class);}

http://www.ppmy.cn/news/107316.html

相关文章

python常用库汇总

文章目录 文件处理图像处理大数据与科学计算人工智能与机器学习数据库网 络Web 框架安 全 Chardet→字符编码探测器&#xff0c;可以自动检测文本、网页、xml的编码。 colorama →主要用来给文本添加各种颜色&#xff0c;并且非常简单易用。 Prettytable → 主要用于在终端或浏…

背靠背储能变流器的原理分析

背靠背变流器是一种用于电力转换的设备,通常由两个相互独立的变流器组成,并通过一定的控制方式进行连接和协调工作。它可以将直流电源转换为交流电源,并具有一定的功率因数调节和电网调节功能。其主要应用领域包括太阳能、风能、能源储存等方面。 背靠背变流器是一种采用对称…

Promise.all()和Promise.race()

Promise.all接收的是数组&#xff0c;得到的结果也是数组&#xff0c;并且一一对应&#xff0c;也可以理解为Promise.all照顾跑的最慢的&#xff0c;最慢的跑完才结束。Promise.race接收的也是数组&#xff0c;不过&#xff0c;得到的却是数组中跑的最快的那个&#xff0c;当最…

【Linux】iptables 防火墙(SNAT/DNAT)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、SNAT 原理与应用二、SNAT转换三、DNAT的介绍1.DNAT概述2.DNAT转换前提条件 四、DNAT转换五、防火墙规则的备份和还原六、tcpdump抓包工具的运用 一、SNAT 原理与…

Form Generator扩展 el-select 数据字典

一、form-generator是什么?✨ ⭐️ 🌟 form-generator的作者是这样介绍的:Element UI表单设计及代码生成器,可将生成的代码直接运行在基于Element的vue项目中;也可导出JSON表单,使用配套的解析器将JSON解析成真实的表单。 但目前它提供的组件并不能满足我们在项目中的…

K8S的服务质量QoS —— 筑梦之路

K8S中的应用服务质量&#xff08;QoS&#xff09;介绍 服务质量&#xff08;QoS&#xff09;类是Kubernetes的概念&#xff0c;它确定Pod的调度和驱逐优先级 Kubelet使用它来管理驱逐pod的顺序&#xff0c;以及使用高级CPU管理策略允许更复杂的pod调度决策。 QoS由Kubernetes本…

国产替代10BASE-T ST7010QNL 应用局域网的以太网变压器/扼流器

Hqst华强盛导读&#xff1a; 华强盛是电子产品国产替代大军中的一员&#xff0c;随着中国电子产业的快速发展&#xff0c;越来越多的电子产品开始出现了国产替代品。这些国产替代品在性能、品质和价格等方面都有了显著的提升&#xff0c;成为了工厂用户的首选。 国产替代10BAS…

JAVA BigDecimal 比较大小 、计算

1&#xff1a;比较大小 注意&#xff1a;使用compareTo&#xff08;&#xff09;方法比较大小时 参与比较的两个值 必须有值 不能为空 BigDecimal a new BigDecimal("3"); BigDecimal b new BigDecimal("4"); if (a.compareTo(b) < 0) { System.…