ArcGis系列-java调用GP分析

news2024/10/6 1:45:24

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");
        //后续操作后台运行taskExecutor
        CompletableFuture.runAsync(() -> submitJobAsync(param), taskExecutor);
    }
	
	/**
     * 异步处理gp任务
     *
     * @param param
     */
	@Async("taskExecutor")
    public void submitJobAsync(GpParam param) {
        String gpId = param.getGpId();
        Map<String, Object> paramMap = new HashMap<>();
        //指定返回格式为json
        paramMap.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/0
                JSONObject 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=json
        url = 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和栅格url
                    if ("GPRasterDataLayer".equals(resJson.getString("dataType"))) {
                        //栅格数据下载tif到本地 https:/127.0.0.1:6443/arcgis/rest/directories/arcgisjobs/gp/demcreate04_gpserver/j6b911eade85d4ed88748abeef0c3a0ac/scratch/dem2.tif
                        //表格GPRecordSet和矢量GPFeatureRecordSetLayer
                        resFilePath = layerParamLocation + gpId + "\\tif\\" + outputName + ".tif";
                        String tifUrl = resJson.getJSONObject("value").getString("url");
                        FileUtils.downloadImage2(tifUrl, resFilePath);
                        //gpResult.setGpResult(tifUrl);
                    } else {
                        //表格GPRecordSet和矢量GPFeatureRecordSetLayer
                        resFilePath = 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.coloradmin.cn/o/589366.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D知识入门

3D场景必备&#xff1a;scene, renderer, light, camera, model 一个基本代码: <script src"https://cdn.bootcdn.net/ajax/libs/three.js/r127/three.min.js"></script>var scene new THREE.Scene();var camera new THREE.PerspectiveCamera(75,windo…

【EKS】基于Amazon EKS搭建kubernetes集群

文章目录 前言 | 亚马逊云科技 re:Invent前沿资讯一、介绍篇&#x1f3a8;什么是AWS 云计算什么是Amazon EKS 二、部署篇&#x1f528;1、创建集群VPC2、创建集群子网3、创建IGW网关4、创建路由表与子网绑定5、EKS集群创建6、创建kubeconfig配置文件7、添加计算节点组8、查看EK…

IC卡水表大多都用在什么项目上?有什么功能特点吗?

IC卡水表是一种先进的计量仪表&#xff0c;广泛应用于许多项目&#xff0c;其功能特点使其在许多领域得到广泛应用。 首先&#xff0c;IC卡水表可以应用于自来水的计量&#xff0c;它可以高精度地测量水的流量&#xff0c;提供给用户准确的用水量信息&#xff0c;从而有助于用户…

分片架构,Redis Cluster 分析

分片架构解决的问题 通过堆机器&#xff0c;提升读写性能&#xff0c;与存储性能 分片架构设计要点 分片规则 选择Cardinality大的作为分片键&#xff0c;尽可能保证数据分布均匀 常见分片键&#xff1a; 基于主键&#xff08;业务型数据&#xff09;&#xff0c;基于时间…

JavaScript高级四、高阶技巧

零、文章目录 JavaScript高级四、高阶技巧 1、深浅拷贝 首先浅拷贝和深拷贝只针对引用类型 &#xff08;1&#xff09;浅拷贝 浅拷贝&#xff1a;拷贝对象的属性的值&#xff08;简单类型存的值就是值本身&#xff0c;引用类型存的值是对象的堆地址&#xff09;&#xff0c…

windows里怎么杀死一个进程?

我们可以使用 taskkill 命令&#xff0c;可以使用该工具按照进程 ID (PID) 或映像名称终止任务。 显示帮助消息&#xff1a; taskkill /?参数列表&#xff1a; /S&#xff1a;system&#xff1a;指定要连接的远程系统。/U&#xff1a;[domain\]user&#xff1a;指定应该在哪…

【ESP-01S / ESP8266 AT指令连接阿里云物联网平台】

ESP-01S / ESP8266 AT指令连接阿里云物联网平台 阿里云物联网平台新建设备获取AT参数 AT指令介绍连接阿里云AT指令介绍MQTT固件固件下载硬件连接固件烧录 串口助手调试硬件连接测试指令 AT_Command移植总结问题排查 源码获取 关注星标公众号&#xff0c;不错过精彩内容 作者 | …

【简单实用框架】【十大排序算法直接调用】【可移植】

☀️博客主页&#xff1a;CSDN博客主页&#x1f4a8;本文由 萌萌的小木屋 原创&#xff0c;首发于 CSDN&#x1f4a2;&#x1f525;学习专栏推荐&#xff1a;面试汇总❗️游戏框架专栏推荐&#xff1a;游戏实用框架专栏⛅️点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd;&#…

JeecgBoot企业级开发中实现自定义导出EXCEL的前端表格字段功能

文章目录 如何在后端实现导出前端列表字段到Excel功能需求前端的实现1. 提供一个导出的点击函数2.引入组件中的userMethod3.tableProps中导出中添加对应的查询参数4. 编写导出函数 后端逻辑的实现1.Controller层2.创建Modal类3.Sevice层 检验成果总结 如何在后端实现导出前端列…

【Android Gradle 插件】更新依赖方式,同时解决github三方库引用无法使用问题

首先看一下完整的 settings.gradle 依赖介绍 /* pluginManagement 脚本块,用于配置Gradle插件的Maven仓库,配置的是构建过程中,使用的仓库 ; pluginManagement 脚本块中的 repositories 配置 , 对应之前的 buildscript 中的 repositories 配置 ; */ pluginManagement {reposit…

技术分享 | 一文了解 MySQL Optimizer Trace 的神奇功效

作者&#xff1a;Mutlis CSDN & 阿里云 & 知乎 等平台优质作者&#xff0c;擅长Oracle & MySQL等主流数据库系统的维护和管理等 本文来源&#xff1a;原创投稿 前言 对于 MySQL 5.6 以及之前的版本来说&#xff0c;查询优化器就像是一个黑盒子一样&#xff0c;…

迪赛智慧数——柱状图(基本柱状图):购买雪糕考虑的因素

效果图 冰淇淋季节来袭&#xff0c;因其细腻凉爽的口感和浓郁的口味被广大消费者所钟爱&#xff0c;近年来已经从一款传统的解暑冷冻饮品转变为一种原料丰富、口味多元、追求健康、愉悦和高品质生活方式的休闲食品。据数据显示&#xff0c;82.2&#xff05;女性、82.3%男性消费…

chatgpt赋能python:Python中乘方的介绍

Python中乘方的介绍 在Python中&#xff0c;乘方运算指数运算&#xff0c;常用符号为“”&#xff08;例如2的3次方为23&#xff09;。使用乘方运算可以快速地进行数值计算&#xff0c;尤其是在科学和工程领域中。 为什么要使用乘方运算&#xff1f; 乘方运算主要用于处理大…

NeRF算法

Instant-ngp Instant-ngp简单介绍 Instant-ngp论文链接 英伟达实现的github链接 taichi实现Instant-ngp taichi实现的github链接 渲染 采用体素渲染方法&#xff0c;从相机光线出发&#xff0c;逐步采样3D场景中的三维坐标点的颜色&#xff0c;即可渲染出3D画面。如果直接将3…

进阶神册,Redis+Nginx+设计模式+Spring全家桶+Dubbo核心技术笔记

最近花了很长的时间去搜罗Java核心技术好文&#xff0c;我把每个Java核心技术的优选文章都整理成了一个又一个的文档。昨天也是终于全部整理好了&#xff0c;今天就把这些东西分享给老铁们&#xff0c;也能为老铁们省去不少麻烦&#xff0c;想学什么技能了&#xff0c;遇到哪方…

vue3组件通信详解

vue3组件通信方式有以下几种&#xff1a;porps&#xff0c;$emit&#xff0c; bus&#xff0c;v-model&#xff0c;useAttrs&#xff0c;$ref/$parent&#xff0c;provide/inject&#xff0c;pinia&#xff0c;slot。下面将逐一讲解。 目录 1.porps&#xff1a;实现父子组件通…

V7.0_增加消息队列功能

一&#xff0c;功能描述 增加消息队列&#xff1b;使用自定义copy功能时&#xff0c;子进程copy结束后向父进程发送消息&#xff08;通过消息队列&#xff09;然后exit&#xff1b;此时因wait&#xff08;&#xff09;而处于阻塞态的父进程终于解除了阻塞并且从队列中读取到消…

【云原生-K8s-1】kubeadm搭建k8s集群(一主两从)完整教程及kubernetes简介

&#x1f341;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; 文章目录 Kubernetes简介1 kubernetes架构1.1m…

清凉一夏小风扇-React版

这里写目录标题 前言 一、效果二、代码分享三、总结 前言 本片文章主要是做一个小练习&#xff0c;通过react来制作一个风扇练习css动画。 vue3实现部分看这里–> 一、效果 二、代码分享 1、主体框架 “react”: “^18.2.0” “sass”: “^1.62.1” 2、主要技术点 使用事…

企业上云容灾如何实现碳中和?

随着能源成本的增加和数据消费的激增&#xff0c;“电耗”和“碳排放”成为今年世界移动通信大会热议的话题。目前&#xff0c;ICT行业耗电量约占全球用电量的7%。预计到2040年&#xff0c;ICT行业碳排放量占全球排放量的比例将上升至14%。 容灾是企业为了在灾难时保证业务继续…