# 启动模型计算

作者:于佳鑫

因为这个地方比较复杂而且涉及到的地方比较多,所以单独独立成一篇文章来介绍,下面先看一下这部分的流程图。

图片2

启动模型计算这一部分在后台主要做了两件事:1. 调用模型对用户上传的数据进行计算 2. 调用自动发服务模块将用户上传的 gdb 文件发布成服务。

image-20200918135523067

# 1.1 自动发服务

此部分涉及到的表:

MapServerLog(存放发布服务时的日志)

注:只列出部分重要的字段。

字段名 描述 备注
reviewTaskId 审查任务 id
sourceFileId 发布服务的数据源文件 id(ResourceFile 表) 这个就是矢量数据下的那个 gdb 文件的 id
publishStatus 发布状态 0 -> 以发布,未检测,此时前端会提示“服务正在发布中,请等待!”
1 -> 已检测,发布成功
2 -> 已检测,发布失败,此时前端会提示“是否重新发服务”
serviceName 服务名称 前端最终通过这个拼接出地址调用发布出来的服务

代码逻辑:

MapPublishServiceImpl.java

/**
 * 发布地图服务
 */
@Async
public void publishMapService(Long reviewTaskId) {
  // 如果之前已经发布过服务,则清楚之前发布的痕迹
  this.cleanUpMapServiceData(reviewTaskId);
  // 发布服务,此时会记录日志,publishStatus 的值为 0
  this.publish(reviewTaskId);
  // 检查服务发布状态(死循环),如果成功将 publishStatus 的值改为 1,失败将 publishStatus 的值改为 2
  this.checkPublishStatus(reviewTaskId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 1.2 启动模型计算

涉及到的表:

ModelCalcDatasourceLog(存放模型计算的数据源信息)

字段名 描述 备注
reviewTaskId 审查任务 id 一个审查任务的成果包中可能有多个数据源
sysCode 数据源 code 调用模型上传数据源,模型返回的唯一标识
datasourceType 数据源类型 标识着数据源在成果包的哪个位置

ModelCalcLog(模型计算任务日志)

字段名 描述 备注
reviewTaskId 审查任务 id
taskInstanceId 任务实例 code 模型计算任务的唯一标识
reviewPointId 审查要点 id
calcStatus 模型计算状态 0 -> 计算中
1 -> 计算成功
2 -> 计算失败
3 -> 未计算,出现的原因:在数据库中配置了这个模型参与计算,但是却没有给他配置计算任务(ModelCalcTask)image-20200918160209256

ModelCalculationResult(存放模型计算结果,纵表)

字段名 描述 备注
reviewTaskId 审查任务 id
reviewPointId 审查要点 id
key 属性名
value 属性值
position 代表是哪一行的数据

ModelConflictServer(模型计算的差异服务)

字段名 描述 备注
reviewTaskId 审查任务 id
reviewPointId 审查要点 id
serverName 服务名
layerName 图层名

类名的含义

类名 含义
DownloadDatasourceFile 负责寻找到数据源并且下载
AcquireTaskInstanceFunctional 负责封装不同模型的请求参数
DisposeCalcResultFunctional 负责解析不同模型返回的参数
ModelCalcTask 其实它应该叫“模型计算算法”,其中封装了模型系统中每种算法的请求-解析流程。

代码逻辑:

--------------- MmsCalcServiceImpl2

  1. 将审查任务的状态改为【计算中】
  2. 调用 startTaskFaced#batchStartCalcTaskV2() 对模型任务进行计算

--------------- StartTaskFaced

  1. 获取所有计算规则对象,通过审查任务的 planType 和 areaLevel 查询出需要计算的审查要点,并将其与计算规则对象绑定起来。此时如果发现系统中没有配置审查要点对应的“计算规则”,那么将会记录日志 calcStatus = 3(未计算)。
  2. 过滤出“计算规则”对象之后,获取“计算规则”对象所需的数据源,进行数据源的注册。
  3. 启动所有模型的计算(合并发服务除外),调用 StartTaskTemplateMethod

--------------- StartTaskTemplateMethod(这个是模板方法,主要调用的是 ModelCalcTask 中的方法)

  1. 封装参数请求模型(AcquireTaskInstanceFunctional)获取模型任务的实例 code
  2. 记录模型【正在运行中】的日志(ModelCalcLog 表)
  3. 轮训的请求模型任务是否计算成功,成功继续往下走,失败则记录模型【计算失败】
  4. 获取模型结果
  5. 解析模型结果(DisposeCalcResultFunctional)成为我们系统需要的格式
  6. 结果入库

--------------- StartTaskFaced

  1. 启动合并发服务模型的计算,调用 StartTaskTemplateMethod(同 6~11)

# 合并发服务

有的空间性模型需要计算差异图斑,之前也是在 ModelCalculationResult 表中存储的,但是图斑数据量太大了,导致查询比较缓慢,所以新增了一个合并发服务的模型,将这些差异图斑发布成服务由前端调用。所以合并发服务需要在所有空间模型计算完成之后再计算。

前端获取差异图层的接口为:/v2/reviewPoint/conflictServer/{reviewTaskId}/{reviewPointId}

# 前端调用

注:看不懂的话先看常见问题 2

一个模型计算规则由 AcquireTaskInstanceFunctional 和 DisposeCalcResultFunctional 组成,其中解析方式(DisposeCalcResultFunctional)相同的模型计算结果返回给前端时封装的格式也相同,例如:

image-20200921142406784

这个计算规则配置的解析是 6,所以他对应的返回给前端的接口也就是 types6:

image-20200921142544018

# 1.3 常见问题

# 1)如何添加审查要点

ReviewPoint 表

字段名 注释 作用 备注
id 节点 id 系统自动生成
reviewPointName 审查要点名 与模型名一一对应
doubleScreen 是否双屏 控制前端页面双屏展示
parentId 父节点 id 维持树形结构
leafNode 是否叶子节点 审查要点树叶子节点
display 是否显示 控制是否在前端显示该审查要点
fromModelCalculation 是否来自模型计算
reviewPointType 审查要点类型编码 唯一标识 禁止非开发人员修改
position 排序位置 在同一层级中的位置 可控制前端审查要点树显示顺序
subTitle 副标题 前端会显示副标题
modelVersionCode 模型版本 code 与模型匹配唯一标志 严格检查和模型系统中是否一致
belongAreaLevel 适用的地区层级 当前审查要点适用于哪一级别 0:国家级,1:省级,2:市级,3:县级,4:乡镇级
belongPlanType 适用的规划类型 当前审查要点适用于哪一规划类型 100000:总体规划,200000:专项规划,300000:详细规划
participateCalc 控制是否参与计算 控制是否调用模型进行计算
hasService 是否具有差异图斑服务 控制是否参与合并发布服务
needPublicService 是否需要发布服务 空间性要点才需要发服务

新增审查要点的 sql:

insert into ARS_REVIEW_POINT(id, display, doublescreen, frommodelcalculation, leafnode, participatecalc,
                             modelversioncode, parentid, belongarealevel, belongplantype, position, reviewpointname,
                             reviewpointtype, subtitle, hasservice, algorithmname, needpublicservice)
VALUES (<随便写个id,不要重复就好>, 'Y', 'N', '<需要模型计算的审查要点为Y,不来自为 N>', '<叶子节点为Y,非叶子节点为N', 'N', '<模型code>', <父目录的id,没有父目录则为-1>,
        <规划类型(100000:总体规划,200000:专项规划,300000:详细规划)>, <层级(0:国家级,1:省级,2:市级,3:县级,4:乡镇级)>, <同级最大的position+1>, '<审查要点的名字>',
        <随便编一个不要重复就行>, '', 'N', '<算法名称>', '<需要发服务填Y,不需要为N>')
1
2
3
4
5
6

之后再去要点配置模块进行配置就好了。

image-20200921132103539

# 2)如何对接新模型

对接新模型之前一定要确定这个新模型的算法是否已经开发过了,可以问实施这个模型和之前系统上的有没有相同的,确定没有再进行开发。

接下来,我们走一下开发新模型的流程:

image-20200921132918607

从上面的介绍,大家应该知道对接模型的核心是 AcquireTaskInstanceFunctional(封装请求参数) 和 DisposeCalcResultFunctional(解析请求结果) 类,我们先看下 AcquireTaskInstanceFunctional 接口:

public interface AcquireTaskInstanceFunctional {

    /**
     * 获取任务实例code
     *
     * @param reviewTaskId 审查任务id
     * @param reviewPointType    审查要点类型代码
     * @return 模型内边的任务实例code
     */
    String acquireTaskInstances(Long reviewTaskId, Integer reviewPointType);


    /**
     * 获取当前需要的数据源type
     *
     * @return 数据源types
     */
    List<Integer> getDatasourceTypes();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

里面有两个方法,我们先看第二个方法,这个方法是获取这个模型计算时需要的数据源类型,所以首先我们需要知道:我们要对接的新模型需要哪些数据源,这个信息可以通过实施来知道。

我们再看第一个方法,这个方法的作用是封装参数,发送请求获取模型任务实例 code,所以第二步,我们需要知道需要请求的参数是什么样的,这个信息可以通过模型的开发来知道,模型开发可能会给你这样格式的请求参数:

[
  {
    "modelVersionCode": "<模型code>",
    "realtimeAttributes": [
      {
        "name": "accessLayer",
        "value": "{\"name\":\"\",\"source\":\"<数据源的code>\"}"
      },
      {
        "name": "checkedLayer",
        "value": "{\"name\":\"\",\"source\":\"<数据源的code>\"}"
      }
    ]
  }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注:先看下配置文件中配置的这几个地址

image-20200921134932890

写代码前可以先通过这几个接口先检查一下这个新模型是否可以正常的运行并返回结果。

swagger 地址分别为:

http://52.82.98.186:7321/swagger/index.html

http://52.82.98.186:7322/swagger/index.html

有了请求的参数我们就可以编写 AcquireTaskInstanceFunctional 类的实现,通过接口返回的结果我们也能编写 DisposeCalcResultFunctional 了。

之后我们需要新增一个 ModelCalcTask 类,使用 Spring 的依赖注入将刚刚写好的 AcquireTaskInstanceFunctional 和 DisposeCalcResultFunctional 注入进去,像这样:

image-20200921140430612

AbstractModelCalcTask 类还有一个抽象方法 getTypes(),这个方法会返回这个类能够计算的审查要点类型,所以还需要在 ReviewPointTypeProperties 类中添加一个 getTypes10() 方法。

ReviewPointTypeProperties 类对应的是配置文件的这个地方:

image-20200918160209256

最后,我们需要在下面这个地方配置上新写的计算规则:

image-20200921140908913
# 总结:
  1. 问实施/模型开发这个新模型使用到的数据源有哪些
  2. 问模型开发,这个新模型的请求参数是什么样的,与数据源 code 之间的对应关系是啥
  3. 知道了这两点,就可以写 AcquireTaskInstanceFunctional 类的实现了,假设是 AcquireTaskInstanceFunctional10
  4. 从这个可以看到模型计算的结果是什么样的(当然也可以通过接口获取到,上面已经说了),这样就可以写 DisposeCalcResultFunctional 类的实现,假设是 DisposeCalcResultFunctional10。
image-20200921141339560
  1. 有了这两个就可以写 AbstractModelCalcTask 的实现,假设是 ModelCalcTask10,将上面新写的 2 个类通过依赖注入注入进去。
  2. 在 ReviewPointTypeProperties 类中新增 getTypes10() 方法,返回能由新增的计算规则计算的模型的审查要点类型。
  3. 将新写的 ModelCalcTask10 配置到 META-INF/services/com.dist.ars.manager.remote.mms2.modelCalcTask.AbstractModelCalcTask 文件中。

# 文件预览

在我们配置的审查要点中有两项是辅助性模型里面的,计算这两项的目的就是为了对规划成果中 mdb 和 gdb 文件进行预览。

image-20200921143625136
# mdb 文件预览

对应的 Command 为 TableAchievementCommand,这个 command 主要的作用就是根据 reviewTaskId 和 MDB 表格解析对应的 reviewPointId 获取计算结果返回前端。

image-20200921144101753
# gdb 文件预览
image-20200921144352186

所以 gdb 文件预览分为两个部分,一个是获取自动发服务发出来的地图服务,前端请求流程如下:

  1. 请求 /v2/reviewTask/mapService/{reviewTaskId} 获取地图发布状态
  2. 请求 /v1/reviewPoint/serverUrl/{reviewTaskId} 获取服务名称
  3. 根据服务名称拼接地址获取 gdb 服务。

第二个是 GDB 图层解析模型计算出来的几个图层信息,对应的 Command 为 VectorDataCommand。

image-20200921144655354

注意:因为自动发出来的服务也有图层信息,如果我们返回给前端图层信息数组不为空,那么将会取交集,如果返回的数组为空,则会直接使用发出来服务的图层信息。