js查找元素第一次为null 第二次才显示_当iModel.js遇到CSV数据之二:定位模型

编者注:

在上篇中,叶子老师介绍了如何使用iModel.js读取CSV数据,在本篇中,我们将介绍如何利用iModel.js定位iModel中的三维模型,以进行下一步的数据挂接。原文版由Roop Saini发布在Medium.com上,叶子老师做了本地化的翻译和整理。

731eb9063e18e412120a35fb97e1a040.png

iModel.js是什么? Bentley iTwin数字孪生解决方案提供的开源开发工具包,用户可以利用它,既可以在已有数字孪生产品上进行功能拓展,又可以全新开发自己的数据孪生服务应用。 CSV是一种简单的结构化序列数据。在本例中,我们将介绍如何利用iModel.js将CSV数据与三维模型连接起来。这是建立智能基础设施最基础的使用,也是数据集成的常规应用。

本应用主题分三篇介绍,本文为第二篇

  • 当iModel.js遇到CSV数据之一:RPC读取(点击浏览)

  • 当iModel.js遇到CSV数据之二:定位三维模型

  • 当iModel.js遇到CSV数据之三:突出显示数据

以下为叶子老师的翻译。


在上一篇文章中,我们讨论了如何从iModel.js通过自定义RPC接口从后端读取CSV文件。现在前端得到了这些数据,现在可以开始做些有趣的事情了,看看我们的数据。

d2bfd6a71fd7f4c0074d927f70a683fe.png

构件ID与三维模型中的元素相关联,我们下一步是找到这些元素的精确坐标。这些信息包含在iModel中,但是我们究竟如何获得它呢?

回答:ECSql----iModel的数据库查询语言。

我们可以在前端使用iModel查询API,传入ECSql语句,结果将为CSV中列出的每个垫片提供坐标。但是我们如何写出查询语句呢?通过iModel Console App!这个基于web的客户端静静地坐在那里几个小时,等待着您登录它,这样您就可以打开iModel,编写ECSql查询语句,帮助您发现数据的确切内容。

让我们关注一下这没人搭理的小东西。首先,我们将登录应用程序并打开我们的目标项目和iModel:

e8536fde08493dfb73906a01836e39c2.png

接下来,我们需要运行一些组合的ECSql语句。这应该很简单…我们需要的是具有给定控件ID的垫圈的原点。

为了构造这个查询语句,我们需要这个控件id属性所归属的表(schema)和类(class)。

我知道表AutoPlantPDWPersistenceStrategy的PipingComponent类中有一个component_id(控件Id)属性。我还知道PipingComponent继承自PhysicalElement类并包含PhysicalElement包含的所有属性。其中一个属性是原点,它应该包含我们要查找的元素的精确坐标。

让我们试着从列表以控件ID“AT HRZF7AQ5_4II”找到其中一个组件的原点:

5c7d43cc9a9ef6d64dc0dd7fb51a8ad1.png

有好消息和坏消息:我们找到了一个元素;ECInstanceId是他的唯一标识。然而,原点居然是Null?

可能管道组件可能是一个单元cell,也就是说,它是一个物理元素,同时它也是一个或多个物理元素的父元素,这些物理元素包含我们需要的实际几何信息。是的,很有可能。如果是这样,管道组件将有一个指向其子元素的ElementOwnsChildElement 关联关系。

(译者备注:关于assembly的详细解释可以参见优先社区对此详细介绍的一篇文章)

https://communities.bentley.com/communities/other_communities/chinafirst/w/chinawiki/51424/cell-itwin

iModel中的关联关系是从SourceECInstanceId(s)指向TargetECInstanceId(s),对于ElementOwnsChildElement ,源ID标识父对象,目标ID标识子对象。

让我们使用上面的ECInstanceId来查看垫圈是否有子元素:

d2aa2668d32270c940ed98b6d0cc56e6.png

啊哈!是的。我们有子元素的(Target)ECInstanceId,它可能是physicaleElement。

现在,让我们看看是否可以用它来找到我们的原点:

5b19b64ddc238470068a65973967b8ae.png

就在这里!我们现在得到了位置信息和ECSql查询语句,也没费什么事儿。

现在我们可以单独查询每个组件的位置。但是,每次从前端调用查询API时,我们都要往返于前后端。我们需要三个单独的查询来从组构件id一直到原点。如果不优化,22个组件就是调用66次前后端接口!

想象一下,在高延迟的网络环境下运行,来回66次仅仅用来填充一个数组!

11b59a9a1f7e10eddec55cbacea6fcf8.png

看看能不能一次搞定。我们需要:

1. 将表PipingComponent和 ElementOwnsChildElements表联查,其中PipingComponent的ECInstanceId等于ElementOwnsChildElements中的SourceECInstanceId(父元素)。

2. 将生成的表和physicalement表联查,其中physicalement的ECInstanceId等于ElementOwnsChildElements中的TargetECInstanceId(子元素)。

3. 查询组合表以获得一个新列表,其中列出了组件的原点,这些原点与我们文件中的component_id一一对应。

薅了一阵头发,在控制台上尝试了一番之后,我最终得出了这样的结论:

fe32dfdaf5f74e25cb59487335b62cd3.png

让我们把它写进我们的代码里。我们可以在打开iModel之后,也就是App.tsx里的_onIModelSelected函数下加上这些语句:

      let componentList = "(";      const count = info.length;      // prepare list of component ids      info.forEach((value: any, index: number) => {        componentList += "'" + value.component_id + "'";        componentList += (++index !== count) ? ", " : ")";      });      const query = `SELECT piping.Component_id, physical.Origin        FROM AutoPlantPDWPersistenceStrategySchema.PipingComponent piping        JOIN Bis.ElementOwnsChildElements link ON piping.ECInstanceId = link.SourceECInstanceId        JOIN Bis.PhysicalElement physical ON link.TargetECInstanceId = physical.ECInstanceId        WHERE piping.Component_id IN ${componentList}`;      const rows = [];      for await (const row of imodel.query(query)) rows.push(row);

编译运行的结果是这样的

53262951b1eced4aa00615d3ab873a2c.png

太好了!我们现在可以从结果中解析出原点,并将它们添加到从CSV获得的原始数组中:

    rows.forEach((row) => {      const index = info.findIndex((x) => x.component_id === row.cOMPONENT_ID);      if (index > 0) info[index].position = row.origin;    });

让我们看看最终结果是什么:

46f549a711528721b328ef63b26f9f92.png

很完美!一切数据都集中在一个地方。

有趣的是,我刚刚意识到,当我们从自定义RPC函数中查询CSV数据时,就已经开始往返于前后端了,可以把刚才所有这些逻辑都扔到RPC中,这样就又可以省下一次前后端的接口调用。

让我们把查询代码移到RPC实现类里,并直接从函数fetchInfo中直接调用它:

export class FileReaderRpcImpl extends FileReaderRpcInterface {  public static register() { RpcManager.registerImpl(FileReaderRpcInterface, FileReaderRpcImpl); }  private _filePath = "assets/testdata.csv";  public async fetchInfo(_token: IModelToken): Promise<any[]> {    const data: string = fs.readFileSync(this._filePath, "utf8");    let info = parse(data, {delimiter: ",", columns: ["status", "component_id"]});    info = await this.fetchPositions(info, _token);    return info;  }  private async fetchPositions(info: any[], token: IModelToken) {    let componentList = "(";    const count = info.length;    // prepare list of component ids    info.forEach((value: any, index: number) => {      componentList += "'" + value.component_id + "'";      componentList += (++index !== count) ? ", " : ")";    });    const query = `SELECT piping.Component_id, physical.Origin      FROM AutoPlantPDWPersistenceStrategySchema.PipingComponent piping      JOIN Bis.ElementOwnsChildElements link ON piping.ECInstanceId = link.SourceECInstanceId      JOIN Bis.PhysicalElement physical ON link.TargetECInstanceId = physical.ECInstanceId      WHERE piping.Component_id IN ${componentList}`;    const imodel = IModelDb.find(token);  // THIS ONE    const rows = [];    for await (const row of imodel.query(query)) rows.push(row);    rows.forEach((row) => {      const index = info.findIndex((x) => x.component_id === row.cOMPONENT_ID);      if (index > 0) info[index].position = row.origin;    });    return info;  }}

这里唯一的比较新的地方是imodelDb句柄(第27行)。在前面的例子中,我们使用的是iModelConnection,前端API通过它来访问已经打开的iModel。IModelDb是它的后端对应的名称,传递到RPC接口的token变量允许我们像前端一样对iModel进行查询。

现在前端只需要调用fetchInfo函数就可以了:

16caea576577c2481f6db79f47585388.png

砰!一次接口调用还只有一行代码(程序员的成就感你懂的~)。

这就是我们的最终数组的样子,现在用于演示的所有信息都搞到手了,这将(希望)让这些客户惊喜连连。

最后一个任务是在模型上显示这些数据

bc6e41012075913528866e0f6d7286ac.png

别担心,咱们歇歇喝口水,下次我们将介绍如何将这些数据显示在三维模型上。

iModel.js相关内容:

  • 5分钟快速部署iModel.js服务

  • 快速使用iModel.js

  • 当iModel.js遇到CSV数据之一:RPC读取

iTwin数字孪生相关内容:

  • iTwin性能测试报告:平台属性支撑业务拓展

  • Bentley iTwin数据管理:连接、同步、变更

  • 从WorkSet到iTwin Service: 3/3-Design Review服务

- END - 

?

5366423a1bdb4b4419047aa9c50857b1.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
System.InvalidOperationException: The LINQ expression 'DbSet<z_tjdj_yydj>() .Where(z => z.YYBH.Substring( startIndex: 0, length: z.YYBH.Length - 6) == __Substring_0) .Max(z => int.Parse(z.YYBH.Substring( startIndex: 10, length: 5)))' could not be translated. Additional information: Translation of method 'int.Parse' failed. If this method can be mapped to your custom function, see https://go.microsoft.com/fwlink/?linkid=2132413 for more information. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information. at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|15_0(ShapedQueryExpression translated, <>c__DisplayClass15_0& ) at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node) at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query) at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression) at System.Linq.Queryable.Max[TSource,TResult](IQueryable`1 source, Expression`1 selector) at VOL.TJYY.Services.z_tjdj_yydjService.<>c__DisplayClass10_0.<Import1>b__0(List`1 list) at VOL.Core.BaseProvider.ServiceBase`2.Import(List`1 files) in D:\work\TJYYHT_VOL\Net6版本\VOL.Core\BaseProvider\ServiceBase.cs:line 459 at VOL.TJYY.Services.z_tjdj_yydjService.Import(List`1 files) at VOL.TJYY.Services.z_tjdj_yydjService.Import1(List`1 fileInput, Dictionary`2 data) at VOL.TJYY.Controllers.z_tjdj_yydjController.Import1(List`1 fileInput, Dictionary`2 data) at lambda_method901(Closure , Object , Object[] ) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync() at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
06-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值