-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
573 lines (317 loc) · 573 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>HPDell 的个人博客</title>
<subtitle>我们在小孩和大人的转角盖一座城堡</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://hpdell.github.io/"/>
<updated>2022-04-14T16:50:55.409Z</updated>
<id>http://hpdell.github.io/</id>
<author>
<name>hpdell</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>使用 CMake 统一管理并编译 C++/Python/R 算法包</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/cmake-cpp-pypi-cran/"/>
<id>http://hpdell.github.io/编程/cmake-cpp-pypi-cran/</id>
<published>2022-04-14T17:46:17.000Z</published>
<updated>2022-04-14T16:50:55.409Z</updated>
<content type="html"><![CDATA[<p>在数据分析领域,Python 和 R 都是比较常用的语言。这两种语言在使用上有很多的相似处,也有很多的不同。一方面,这两个语言对于代码的执行效率都远远不如静态语言(如C++),尤其是循环的效率、矩阵运算的效率等。另一方面,这两种语言使用起来都要更为方便,而且有许多其他的软件包可以使用,很容易就可以和其他算法一起使用,这点又是 C++ 这种静态语言不能比的。所以长久以来形成了“用 Python 和 R 调用 C++ 计算”的模式,以发挥两类语言各自的特点。Python 中可以使用 pybind 或者 Cython 调用 C++ 代码,而 R 可以用 Rcpp 调用 C++ 代码。目前很多算法库都有 Python 和 R 版本,但是往往都是单独开发,甚至 Python 版本和 R 版本不是同一个作者。为了解决这个问题,笔者尝试了使用 CMake 将算法计算部分的 C++ 代码和调用部分的 Python 与 R 代码统一管理,使开发者可以同时提供两种语言的版本。</p><h1 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h1><p>本项目主要采用这样一种结构:</p><ul><li><code>/</code> 根目录<ul><li><code>CMakeLists.txt</code> 主 CMake 配置文件</li><li><code>include</code> C++ 头文件</li><li><code>src</code> C++ 源文件</li><li><code>test</code> C++ 单元测试</li><li><code>python</code> Python 模块代码<ul><li><code>mypypackage</code> Python 代码,主要包含用于调用 C++ 的 Cython 代码</li><li><code>test</code></li><li><code>CMakeLists.txt</code> Python 模块的 CMake 配置文件</li><li><code>setup.py</code> 用于构建和发布的 scikit-build 脚本</li></ul></li><li><code>R</code> R 包代码<ul><li><code>data</code> 用于存放包提供的数据文件</li><li><code>man</code> 用于存放其他文档</li><li><code>R</code> 用于调用的 R 代码</li><li><code>src</code> 用于调用库的 C++ 代码</li><li><code>CMakeLists.txt</code> R 包的 CMake 配置文件</li><li><code>DESCRIPTION.in</code> R 包 DESCRIPTION 模板,在 CMake 项目配置时自动填入版本号等信息</li><li><code>NAMESPACE.in</code> R 包 NAMESPACE 模板,在 CMake 项目配置时自动填入版本号等信息</li></ul></li></ul></li></ul><p>根目录中可以添加一些持续集成配置文件、文档源文件等其他文件。</p><p>总体上,该项目结构是一个 C++ 项目的格式,在开发时也是先开发 C++ 代码,在 C++ 代码的基础上再开发 Python 或 R 代码,甚至其他语言的代码。</p><h1 id="设计思路"><a href="#设计思路" class="headerlink" title="设计思路"></a>设计思路</h1><p>在这个包中,根目录中的 C++ 代码主要负责实现算法内核的部分,即与所有调用语言无关的东西。在这里面,不能使用 Python 中的 DataFrame 或者 R 中的任何类型,只能使用纯 C++ 支持的类型。也就是说,需要调用者在 C++ 程序中可以直接调用这个库。这个算法核心部分通过 <code>/test</code> 目录下的代码进行单元测试,只要测试通过就说明算法核心没有问题。</p><p>目录 <code>Python</code> 和 <code>R</code> 中的代码主要是提供这些语言对于调用 C++ 代码的支持。一般情况下,都是这样一个顺序:Python 或 R 函数调用中间件、中间件调用 C++ 库。所以这两个目录中就需要包含 Python 或 R 函数(简称包函数)以及中间件这两个部分。在 Python 中,中间件往往是用 Cython 或者 Pybind 编写的,为包函数提供了 Python 对象和 C++ 对象进行对接的能力;在 R 中,中间件往往是用 C++ 编写的,依靠 Rcpp 包提供的能力,将 R 语言对象转换为 C++ 对象,并调用 C++ 库函数。</p><h1 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h1><p>为了描述方便,我们将 <code>CMakeLists.txt</code> 文件统称为配置文件。</p><p>根配置文件的编写比较简单,主要就是设置一些变量,例如是否有 Python 模块的 <code>WITH_PYTHON</code> 等,然后添加一些目录。下面是一个示例</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /CMakeLists.txt</span></span><br><span class="line"><span class="keyword">cmake_minimum_required</span>(VERSION <span class="number">3.12</span>.<span class="number">0</span>)</span><br><span class="line"><span class="keyword">project</span>(myproject VERSION <span class="number">0.1</span>.<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span>(CMAKE_CXX_STANDARD <span class="number">11</span>)</span><br><span class="line"><span class="keyword">set</span>(CMAKE_MODULE_PATH <span class="variable">${CMAKE_SOURCE_DIR}</span>/cmake)</span><br><span class="line"></span><br><span class="line"><span class="keyword">option</span>(WITH_R <span class="string">"Whether to build R extension"</span> <span class="keyword">OFF</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span>(TEST_DATA_DIR <span class="variable">${CMAKE_SOURCE_DIR}</span>/<span class="keyword">test</span>/data)</span><br><span class="line"></span><br><span class="line"><span class="keyword">add_subdirectory</span>(src)</span><br><span class="line"></span><br><span class="line"><span class="keyword">include</span>(CTest)</span><br><span class="line"><span class="keyword">enable_testing</span>()</span><br><span class="line"></span><br><span class="line"><span class="keyword">add_subdirectory</span>(<span class="keyword">test</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">set</span>(CPACK_PROJECT_NAME <span class="variable">${PROJECT_NAME}</span>)</span><br><span class="line"><span class="keyword">set</span>(CPACK_PROJECT_VERSION <span class="variable">${PROJECT_VERSION}</span>)</span><br><span class="line"><span class="keyword">include</span>(CPack)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(WITH_R)</span><br><span class="line"> <span class="keyword">add_subdirectory</span>(R)</span><br><span class="line"><span class="keyword">endif</span>()</span><br></pre></td></tr></table></figure><p>主要工作就是向根配置文件中,引入 C++ 配置文件和测试用例,以及当 <code>WITH_R=ON</code> 时引入 R 配置文件。</p><h2 id="C-配置文件"><a href="#C-配置文件" class="headerlink" title="C++ 配置文件"></a>C++ 配置文件</h2><p>这部分的配置文件写法和一般 CMake 管理的 C++ 库没有什么区别,所做的就是查找一些库、构建库或可执行程序。可以无需考虑 Python 或 R 的部分。</p><h2 id="R-配置文件"><a href="#R-配置文件" class="headerlink" title="R 配置文件"></a>R 配置文件</h2><p>R 配置文件相对比较简单一些。由于 R 有自己的包结构,如果要发布到 CRAN 中的话,就需要按照这种结构来提交。而且 R 包的安装是通过 <code>R CMD INSTALL</code> 命令进行安装的,不太适合用文件拷贝的方式进行安装。那么我们可以根据现有的代码结构,单独使用一个文件夹,程序化构造 R 包的结构,并调用 R 相关命令进行包的构建。于是我们可以充分利用 CMake 提供的文件操作命令以及 <code>add_custom_target()</code> 方法实现这一目的。</p><p>在 CMake 配置和生成阶段,我们可以使用以下命令来生成一个 R 包的标准结构。</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /R/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">set</span>(PROJECT_RBUILD_DIR <span class="variable">${CMAKE_BINARY_DIR}</span>/<span class="variable">${PROJECT_NAME}</span>)</span><br><span class="line"><span class="keyword">make_directory</span>(<span class="variable">${PROJECT_RBUILD_DIR}</span>)</span><br><span class="line"><span class="keyword">configure_file</span>(DESCRIPTION.in <span class="variable">${PROJECT_RBUILD_DIR}</span>/DESCRIPTION)</span><br><span class="line"><span class="keyword">configure_file</span>(NAMESPACE.in <span class="variable">${PROJECT_RBUILD_DIR}</span>/NAMESPACE)</span><br><span class="line"><span class="keyword">file</span>(COPY cleanup DESTINATION <span class="variable">${PROJECT_RBUILD_DIR}</span>)</span><br><span class="line"><span class="keyword">file</span>(COPY configure DESTINATION <span class="variable">${PROJECT_RBUILD_DIR}</span>)</span><br><span class="line"><span class="keyword">file</span>(COPY configure.ac DESTINATION <span class="variable">${PROJECT_RBUILD_DIR}</span>)</span><br><span class="line"><span class="keyword">file</span>(COPY <span class="variable">${CMAKE_SOURCE_DIR}</span>/R/R DESTINATION <span class="variable">${PROJECT_RBUILD_DIR}</span>)</span><br><span class="line"><span class="keyword">file</span>(COPY <span class="variable">${CMAKE_SOURCE_DIR}</span>/R/src DESTINATION <span class="variable">${PROJECT_RBUILD_DIR}</span>)</span><br><span class="line"><span class="keyword">file</span>(COPY <span class="variable">${CMAKE_SOURCE_DIR}</span>/R/man DESTINATION <span class="variable">${PROJECT_RBUILD_DIR}</span>)</span><br><span class="line"><span class="keyword">file</span>(COPY <span class="variable">${CMAKE_SOURCE_DIR}</span>/R/data DESTINATION <span class="variable">${PROJECT_RBUILD_DIR}</span>)</span><br><span class="line"><span class="keyword">file</span>(COPY <span class="variable">${CMAKE_SOURCE_DIR}</span>/<span class="keyword">include</span>/header.h DESTINATION <span class="variable">${PROJECT_RBUILD_DIR}</span>/src)</span><br><span class="line"><span class="keyword">file</span>(COPY <span class="variable">${CMAKE_SOURCE_DIR}</span>/src/sources.cpp DESTINATION <span class="variable">${PROJECT_RBUILD_DIR}</span>/src)</span><br></pre></td></tr></table></figure><p>这样在 CMake 构建目录下,会出现一个 <code>${PROJECT_RBUILD_DIR}</code> 的目录,里面就是一个标准结构的 R 包。接下来,所有与 R 相关的操作,就都可以针对这个文件夹进行。下面是一个对 R 包进行编译、生成文档、打包的示例。</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /R/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">add_custom_target</span>(mypackage_rbuild</span><br><span class="line"> VERBATIM</span><br><span class="line"> WORKING_DIRECTORY <span class="variable">${PROJECT_RBUILD_DIR}</span>/..</span><br><span class="line"> COMMAND_EXPAND_LISTS</span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E</span><br><span class="line"> <span class="keyword">make_directory</span> <span class="string">"${PROJECT_NAME}.library"</span></span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${R_EXECUTABLE}</span> CMD <span class="keyword">INSTALL</span> --preclean --clean --library=<span class="variable">${PROJECT_NAME}</span>.library <span class="variable">${PROJECT_NAME}</span></span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${R_EXECUTABLE}</span> -e <span class="string">"roxygen2::roxygenize('${PROJECT_NAME}', load_code = 'source')"</span></span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${R_EXECUTABLE}</span> CMD build <span class="variable">${PROJECT_NAME}</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>基于这种思路,我们同样可以编写一个测试,就用来执行 <code>R CMD check</code> 命令。</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /R/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">add_test</span>(</span><br><span class="line"> NAME Test_R_mypackage</span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${R_EXECUTABLE}</span> CMD check <span class="variable">${PROJECT_NAME}</span>_<span class="variable">${PROJECT_VERSION_R}</span>.tar.gz --as-cran --no-manual</span><br><span class="line"> WORKING_DIRECTORY <span class="variable">${PROJECT_RBUILD_DIR}</span>/..</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>此外,如果使用 VSCode 进行开发,且使用 CMake 作为配置提供工具,使用这种方式会导致自动类型提示无法在 <code>RcppExports.cpp</code> 等 R 包所需的 C++ 文件中工作。解决方法也非常简单,就是写一个正常的 CMake 生成目标即可,但是将这个生成目标排除在 ALL 目标之外。</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /R/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">find_package</span>(R REQUIRED)</span><br><span class="line"><span class="keyword">include_directories</span>(<span class="variable">${R_INCLUDE_DIRS}</span> <span class="variable">${RCPP_ARMADILLO_INCLUDE_DIR}</span> <span class="variable">${RCPP_INCLUDE_DIR}</span>)</span><br><span class="line"><span class="keyword">include_directories</span>(../<span class="keyword">include</span>)</span><br><span class="line"><span class="keyword">add_library</span>(mypackage_rcpp_export SHARED src/RcppExports.cpp)</span><br><span class="line"><span class="keyword">target_link_libraries</span>(mypackage_rcpp_export mylib)</span><br><span class="line"><span class="keyword">set_target_properties</span>(mypackage_rcpp_export PROPERTIES EXCLUDE_FROM_ALL <span class="keyword">TRUE</span>)</span><br></pre></td></tr></table></figure><p>这样,我们就可以完全依靠 CMake 的指令操作所有的流程。例如</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">mkdir build && <span class="built_in">cd</span> build</span><br><span class="line">cmake .. -DWITH_R=ON</span><br><span class="line">cmake --build . --config Release --target mypackage_rbuild</span><br><span class="line">ctest -R Test_R_mypackage --output-on-failure</span><br></pre></td></tr></table></figure><p>在持续集成中也可以采用这样的操作,避免因为 R 解释器以及操作系统的问题,造成很多不必要的麻烦。</p><h2 id="Python-配置文件"><a href="#Python-配置文件" class="headerlink" title="Python 配置文件"></a>Python 配置文件</h2><p>Python 的配置文件会相对来说更复杂一点,因为涉及到 Cython 语言的编译问题。好在 scikit-build 库已经提供了使用 CMake 编译 Cython 文件的方法,而且有打包的功能。那么我们可以直接使用 scikit-build 编译,也可以像 R 包一样,构建一个 scikit-build 所需要的结构,并将这个包作为提交 Pypi 的包。</p><h3 id="使用-CMake-直接编译"><a href="#使用-CMake-直接编译" class="headerlink" title="使用 CMake 直接编译"></a>使用 CMake 直接编译</h3><p>根据 <a href="https://scikit-build.readthedocs.io/en/latest/cmake-modules/Cython.html" target="_blank" rel="noopener">scikit-build 的文档</a>,我们可以用这样的配置直接编译一个 Python 模块(pyd 文件)</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /python/mypackage/CMakeLists.txt</span></span><br><span class="line">add_cython_target(pymypackage.pyx CXX)</span><br><span class="line"><span class="keyword">add_library</span>(pymypackage MODULE <span class="variable">${pymypackage}</span>)</span><br><span class="line"><span class="keyword">target_link_libraries</span>(pymypackage mylib <span class="variable">${ARMADILLO_LIBRARIES}</span> <span class="variable">${Python3_LIBRARIES}</span> Python3::NumPy)</span><br><span class="line">python_extension_module(pymypackage)</span><br></pre></td></tr></table></figure><p>然后在 Python 模块代码的配置文件中引入即可</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /python/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">include_directories</span>(<span class="variable">${MYLIB_INCLUDE_DIR}</span>)</span><br><span class="line"><span class="keyword">add_subdirectory</span>(<span class="string">"mypypackage"</span>)</span><br><span class="line"><span class="keyword">add_subdirectory</span>(<span class="string">"test"</span>)</span><br></pre></td></tr></table></figure><p>编译好的 Python 模块就可以直接通过 <code>import</code> 关键字引入。同样我们可以写一些 install 脚本,这样就可以直接将编译好的包安装在本地。</p><h3 id="使用-Scikit-Build-编译"><a href="#使用-Scikit-Build-编译" class="headerlink" title="使用 Scikit-Build 编译"></a>使用 Scikit-Build 编译</h3><p>与 R 包类似,构建 Pypi 包无非就是拷贝一些文件到一个目录,形成对应的结构。例如我们可以这样</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /python/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">set</span>(PYMYPACKAGE_SKBUILD_DIR <span class="variable">${CMAKE_BINARY_DIR}</span>/pymypackage)</span><br><span class="line"><span class="keyword">add_custom_target</span>(pymypackage_skbuild</span><br><span class="line"> VERBATIM</span><br><span class="line"> COMMAND_EXPAND_LISTS</span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E</span><br><span class="line"> <span class="keyword">make_directory</span></span><br><span class="line"> <span class="variable">${PYMYPACKAGE_SKBUILD_DIR}</span></span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E</span><br><span class="line"> copy_directory <span class="variable">${CMAKE_SOURCE_DIR}</span>/python <span class="variable">${PYMYPACKAGE_SKBUILD_DIR}</span></span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E</span><br><span class="line"> copy_directory <span class="variable">${CMAKE_SOURCE_DIR}</span>/cmake <span class="variable">${PYMYPACKAGE_SKBUILD_DIR}</span>/cmake</span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E</span><br><span class="line"> copy_directory <span class="variable">${CMAKE_SOURCE_DIR}</span>/<span class="keyword">include</span> <span class="variable">${PYMYPACKAGE_SKBUILD_DIR}</span>/<span class="keyword">include</span></span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E</span><br><span class="line"> copy_directory <span class="variable">${CMAKE_SOURCE_DIR}</span>/src <span class="variable">${PYMYPACKAGE_SKBUILD_DIR}</span>/src</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>包结构设置好了,下面就是使用 Scikit-Build 进行编译。虽然 Scikit-Build 最终还是通过 CMake 构建的,但是这种方式支持 python 命令编译安装。我们需要编写 <code>setup.py</code> 和 <code>pyproject.toml</code> 文件。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># setup.py</span></span><br><span class="line"><span class="keyword">from</span> skbuild <span class="keyword">import</span> setup</span><br><span class="line">setup(</span><br><span class="line"> name=<span class="string">"pymypackage"</span>,</span><br><span class="line"> version=<span class="string">"0.2.0"</span>,</span><br><span class="line"> author=<span class="string">"myname"</span>,</span><br><span class="line"> packages=[<span class="string">"pymypackage"</span>],</span><br><span class="line"> install_requires=[<span class="string">'cython'</span>]</span><br><span class="line">)</span><br></pre></td></tr></table></figure><figure class="highlight toml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[build-system]</span></span><br><span class="line"><span class="attr">requires</span> = [</span><br><span class="line"> "setuptools>=42",</span><br><span class="line"> "wheel",</span><br><span class="line"> "scikit-build>=0.12",</span><br><span class="line"> "cmake>=3.18",</span><br><span class="line"> "ninja",</span><br><span class="line"> "cython",</span><br><span class="line"> "numpy",</span><br><span class="line"> "pandas",</span><br><span class="line"> "geopandas"</span><br><span class="line">]</span><br><span class="line"><span class="attr">build-backend</span> = <span class="string">"setuptools.build_meta"</span></span><br></pre></td></tr></table></figure><p>此时如果使用 <code>python setup.py</code> 的方式安装,到此为止只是告诉 Python 要使用 Scikit-Build 安装,以及如何使用这个工具安装。但是还没有告诉 Scikit-Build 怎么去安装。这一步还是通过写 CMake 配置文件进行实现的,通过这个配置文件就告诉 Scikit-Build 使用什么样的步骤构建并编译包。</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /python/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">set</span>(CMAKE_CXX_STANDARD <span class="number">11</span>)</span><br><span class="line"><span class="keyword">set</span>(CMAKE_MODULE_PATH <span class="variable">${CMAKE_CURRENT_SOURCE_DIR}</span>/cmake)</span><br><span class="line"><span class="keyword">find_package</span>(Armadillo REQUIRED)</span><br><span class="line"><span class="keyword">if</span>(MSVC)</span><br><span class="line"> <span class="keyword">add_definitions</span>(-D_CRT_SECURE_NO_WARNINGS)</span><br><span class="line"><span class="keyword">endif</span>(MSVC)</span><br><span class="line"><span class="keyword">set</span>(MYLIB_INCLUDE_DIR <span class="variable">${CMAKE_CURRENT_SOURCE_DIR}</span>/<span class="keyword">include</span>)</span><br><span class="line"><span class="keyword">include_directories</span>(<span class="variable">${MYLIB_INCLUDE_DIR}</span>)</span><br><span class="line"><span class="keyword">add_subdirectory</span>(<span class="variable">${CMAKE_CURRENT_SOURCE_DIR}</span>/src <span class="variable">${pygwmodel_BINARY_DIR}</span>/mylib)</span><br><span class="line"><span class="keyword">add_subdirectory</span>(<span class="string">"pygwmodel"</span>)</span><br><span class="line"><span class="keyword">enable_testing</span>()</span><br><span class="line"><span class="keyword">add_subdirectory</span>(<span class="string">"test"</span>)</span><br></pre></td></tr></table></figure><p>那么问题来了,这段 CMake 配置应该写在哪里呢?应该是 <code>/python/CMakeLists.txt</code> ,因为这个文件是我们 Pypi 包结构的根配置文件。但是构建这个包的 CMake 配置写在哪里呢?还是这个文件,因为 <code>/python</code> 目录是 Python 包代码的根目录。这样就产生了一个冲突,这个文件该如何包含两种配置?</p><p>为了解决这个问题,我们可以使用 Scikit-Build 提供的一个 CMake 宏 <code>SKBUILD</code> 。如果定义了这个宏,那就说明是 Scikit-Build 在使用这个配置文件;如果没有,那就说明不是 Scikit-Build 在使用。但是如果不是 Scikit-Build 在使用,我们依然也需要分成两种情况:直接用 CMake 编译和构建 Pypi 包。因此我们需要再定义一个 <code>USE_SKBUILD</code> 的宏,来区分这两种情况。综合起来,配置文件 <code>/python/CMakeLists.txt</code> 就需要写成下面这个形式</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># /python/CMakeLists.txt</span></span><br><span class="line"><span class="keyword">if</span>(SKBUILD)</span><br><span class="line"> <span class="keyword">set</span>(CMAKE_CXX_STANDARD <span class="number">11</span>)</span><br><span class="line"> <span class="keyword">set</span>(CMAKE_MODULE_PATH <span class="variable">${CMAKE_CURRENT_SOURCE_DIR}</span>/cmake)</span><br><span class="line"> <span class="keyword">find_package</span>(Armadillo REQUIRED)</span><br><span class="line"> <span class="keyword">if</span>(MSVC)</span><br><span class="line"> <span class="keyword">add_definitions</span>(-D_CRT_SECURE_NO_WARNINGS)</span><br><span class="line"> <span class="keyword">endif</span>(MSVC)</span><br><span class="line"> <span class="keyword">set</span>(MYLIB_INCLUDE_DIR <span class="variable">${CMAKE_CURRENT_SOURCE_DIR}</span>/<span class="keyword">include</span>)</span><br><span class="line"> <span class="keyword">include_directories</span>(<span class="variable">${MYLIB_INCLUDE_DIR}</span>)</span><br><span class="line"> <span class="keyword">add_subdirectory</span>(<span class="variable">${CMAKE_CURRENT_SOURCE_DIR}</span>/src <span class="variable">${pygwmodel_BINARY_DIR}</span>/mylib)</span><br><span class="line"> <span class="keyword">add_subdirectory</span>(<span class="string">"pygwmodel"</span>)</span><br><span class="line"> <span class="keyword">enable_testing</span>()</span><br><span class="line"> <span class="keyword">add_subdirectory</span>(<span class="string">"test"</span>)</span><br><span class="line"><span class="keyword">elseif</span>(USE_SKBUILD)</span><br><span class="line"> <span class="keyword">set</span>(PYMYPACKAGE_SKBUILD_DIR <span class="variable">${CMAKE_BINARY_DIR}</span>/pymypackage)</span><br><span class="line"> <span class="keyword">add_custom_target</span>(pymypackage_skbuild</span><br><span class="line"> VERBATIM</span><br><span class="line"> COMMAND_EXPAND_LISTS</span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E</span><br><span class="line"> <span class="keyword">make_directory</span></span><br><span class="line"> <span class="variable">${PYMYPACKAGE_SKBUILD_DIR}</span></span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E</span><br><span class="line"> copy_directory <span class="variable">${CMAKE_SOURCE_DIR}</span>/python <span class="variable">${PYMYPACKAGE_SKBUILD_DIR}</span></span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E</span><br><span class="line"> copy_directory <span class="variable">${CMAKE_SOURCE_DIR}</span>/cmake <span class="variable">${PYMYPACKAGE_SKBUILD_DIR}</span>/cmake</span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E</span><br><span class="line"> copy_directory <span class="variable">${CMAKE_SOURCE_DIR}</span>/<span class="keyword">include</span> <span class="variable">${PYMYPACKAGE_SKBUILD_DIR}</span>/<span class="keyword">include</span></span><br><span class="line"> <span class="keyword">COMMAND</span> <span class="variable">${CMAKE_COMMAND}</span> -E</span><br><span class="line"> copy_directory <span class="variable">${CMAKE_SOURCE_DIR}</span>/src <span class="variable">${PYMYPACKAGE_SKBUILD_DIR}</span>/src</span><br><span class="line"> )</span><br><span class="line"><span class="keyword">else</span>()</span><br><span class="line"> <span class="keyword">include_directories</span>(<span class="variable">${MYLIB_INCLUDE_DIR}</span>)</span><br><span class="line"> <span class="keyword">add_subdirectory</span>(<span class="string">"mypypackage"</span>)</span><br><span class="line"> <span class="keyword">add_subdirectory</span>(<span class="string">"test"</span>)</span><br><span class="line"><span class="keyword">endif</span>()</span><br></pre></td></tr></table></figure><p>这样,就可以用这一个配置文件,实现三种不同的构建。如果只需要本地构建,配置 CMake 的时候带上 <code>-DWITH_PYTHON=ON</code> 参数;如果需要构建包结构,带上 <code>-DWITH_PYTHON=ON -DUSE_SKBUILD=ON</code> 参数,然后使用 Python 解释器运行 <code>setup.py</code> 脚本就可以构建了。</p><h1 id="参考仓库"><a href="#参考仓库" class="headerlink" title="参考仓库"></a>参考仓库</h1><p>关于 Python 部分,可以参考仓库 <a href="https://github.com/HPDell/libgwmodel" target="_blank" rel="noopener">hpdell/libgwmodel</a>,该仓库是按照本文所描述的方式编写的。关于 R 部分,上述仓库中虽然有,但是方法比较陈旧了,与本文描述也有一定的出入。使用本文方法编写的仓库暂时还不适合开源,但会尽快开源。届时将补充在本文中。</p>]]></content>
<summary type="html">
<p>在数据分析领域,Python 和 R 都是比较常用的语言。这两种语言在使用上有很多的相似处,也有很多的不同。
一方面,这两个语言对于代码的执行效率都远远不如静态语言(如C++),尤其是循环的效率、矩阵运算的效率等。
另一方面,这两种语言使用起来都要更为方便,而且有许多其他的
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="R" scheme="http://hpdell.github.io/tags/R/"/>
<category term="C++" scheme="http://hpdell.github.io/tags/C/"/>
<category term="CMake" scheme="http://hpdell.github.io/tags/CMake/"/>
<category term="Python" scheme="http://hpdell.github.io/tags/Python/"/>
<category term="Cython" scheme="http://hpdell.github.io/tags/Cython/"/>
</entry>
<entry>
<title>原神中为什么暴击暴伤比为1:2最好?</title>
<link href="http://hpdell.github.io/%E7%90%86%E8%AE%BA/genshin-impact-critical/"/>
<id>http://hpdell.github.io/理论/genshin-impact-critical/</id>
<published>2022-03-25T14:42:00.000Z</published>
<updated>2022-04-14T16:50:55.449Z</updated>
<content type="html"><![CDATA[<p>原神中的圣遗物属性搭配是一个既烧摩拉又有趣的问题。相信很多玩家听说过一个结论,“角色面板暴击暴伤比为1:2时,期望伤害最高”。那为什么会得到这个结论呢?这涉及到很多有趣的数学问题,这篇博客中我们尝试进行推导。但是,由于实际角色伤害还取决于攻击力,但是引入攻击力的会使问题变得很复杂,这里就认为攻击力是不变的。确切地说,是在更换不同圣遗物或武器后,角色攻击力保持不变。</p><h1 id="伤害的数学期望"><a href="#伤害的数学期望" class="headerlink" title="伤害的数学期望"></a>伤害的数学期望</h1><p>暴击率、暴击伤害,是动作游戏中很经典的两个属性。暴击率,就是指角色在一次攻击时发生暴击的概率。在原神中,假设一名角色的暴击率是 $p$,暴击伤害是 $q$,在不暴击的时候,伤害总值是 $a$,那么在攻击发生暴击时,当次攻击的伤害变为 $(1+q)a$。那么,对于攻击造成伤害这一事件,其取值 $A$ 服从如下分布列:</p><table><thead><tr><th></th><th>$1-p$</th><th>$p$</th></tr></thead><tbody><tr><td>$A$</td><td>$a$</td><td>$(1+q)a$</td></tr></tbody></table><p>那么一次伤害的数学期望值 $e$ 就有</p><p>$$\begin{aligned}e = E(A)&= (1-p)a+p(1+q)a \\&= a + pqa \\&= (1+pq)a\end{aligned}$$</p><p>在我们当前的假设中,可以认为 $a$ 是一个常数,$p$ 和 $q$ 是两个变量。那么我们分解将 $e$ 对 $p,q$ 求偏导,</p><p>$$\frac{\partial e}{\partial p} = qa, \quad \frac{\partial e}{\partial q} = pa$$</p><p>由于 $a,p,q$ 都是正数,所以理论上,<strong>暴击率越高,暴击伤害越高,伤害期望也越高</strong>。但是这似乎与之前的结论矛盾。为什么会出现这个情况呢?</p><h1 id="伤害期望的约束最大值"><a href="#伤害期望的约束最大值" class="headerlink" title="伤害期望的约束最大值"></a>伤害期望的约束最大值</h1><p>事实上,这是由于原神中“圣遗物”系统和“武器”系统的特性决定的。上述结论没有考虑到暴击率和暴击伤害取值的约束条件。下面先简述一下圣遗物和武器系统,来找到问题中的约束条件。</p><h2 id="圣遗物和武器系统简介"><a href="#圣遗物和武器系统简介" class="headerlink" title="圣遗物和武器系统简介"></a>圣遗物和武器系统简介</h2><p>原神中每个人物最初始的情况下,有 5% 的暴击率和 50% 的暴击伤害。出了每个人物突破会提升暴击率暴击伤害以外,这个数值可以通过装备不同的圣遗物和武器进行提升。原神中每个人物最多装备5件圣遗物和1把武器,每件圣遗物(按五星计算)有1个主属性和4个副属性,每把武器有一个主属性和一个副属性。对于圣遗物而言,有以下几条约束:</p><ul><li>圣遗物中只有“理之冠”(俗称“头”)有可能是暴击率或暴击伤害;每件圣遗物的副属性都有可能是暴击率和暴击伤害。但是所有主属性和副属性都不可能有重复的属性。</li><li>每件圣遗物最多强化到20级,每次升级都会强化主属性,但每提升4级才会提升一次副属性(如果副属性不足4个则有限生成一个副属性)。</li><li>每次强化虽然会强化到不同的属性,但是数值的比例是大概相同的,以使属性“总量”不会发生太大的变化。也就是说,不会出现一次强化出现100%暴击率的情况。</li><li>每次提升属性时,提升到暴击率和暴击伤害的数值大约是 $1:2$ 的比例,也就是说,在一次提升时,如果提升的是暴击率且提升了3.8%,那么如果这次提升发生在暴击伤害上,则提升幅度大约是7.6%。</li></ul><p>对于武器而言,相对来说就简单很多。每把武器的主属性必然是攻击力,副属性虽然可能是暴击率或暴击伤害,但都是确定的。级如果只看副属性是暴击率或暴击伤害的武器(俗称“暴击武器”或“暴伤武器”),平均每次升级时暴击率和暴击伤害提高的数值差不多也是 $1:2$ 的比例。</p><p>在这样的情况下,我们可以假设一个暴击率和暴击伤害的总量 $c$ ,这个量和暴击率与暴击伤害的关系就是</p><p>$$c=2p+q$$</p><p>这将成为我们计算期望伤害最大值的约束条件。但事实上,我们不可能保证两件圣遗物的暴击暴伤总量完全相同,这里只是做一个理论计算,分析暴击率和暴击伤害的最佳配比。</p><h2 id="暴击暴伤总量不变情况下的伤害期望最大值"><a href="#暴击暴伤总量不变情况下的伤害期望最大值" class="headerlink" title="暴击暴伤总量不变情况下的伤害期望最大值"></a>暴击暴伤总量不变情况下的伤害期望最大值</h2><p>根据之前推导的伤害期望 $e$ 与暴击暴伤的函数关系,和暴击暴伤总量 $c$ 与暴击爆伤函数关系的约束条件,有</p><p>$$\begin{aligned}e &= (1+pq)a \\c &= 2p + q\end{aligned}$$</p><p>要求 $e$ 的约束最大值,可以使用<a href="https://baike.baidu.com/item/%E6%8B%89%E6%A0%BC%E6%9C%97%E6%97%A5%E4%B9%98%E6%95%B0%E6%B3%95/8550443" target="_blank" rel="noopener">拉格朗日乘数法</a>,构造 $\phi=2p+q-c$ ,$F=e+\lambda\phi$,可以得到下面的方程组</p><p>$$\begin{aligned}\frac{\partial F}{\partial p} & = qa+2\lambda = 0 \\\frac{\partial F}{\partial q} & = pa+\lambda = 0 \\\frac{\partial F}{\partial \lambda} &= 2p+q -c = 0\end{aligned}$$</p><p>通过解上述方程组可以很容易得到,当 $q=2p=\frac{1}{2}c$ 的时候,函数 $e$ 取得极值,此时 $\lambda=-\frac{1}{4}ca$。而由于极值点只有这么一个,所以这个就是最值点。那么这个是最大值还是最小值呢?</p><p>一种方法是,由于一般 $p \in [0.05,1]$ 而 $q\in[0.5, q_m]$,这里的 $q_m$ 是所有装备暴伤拉满时的数值。这个数值虽然我没有见过,但是肯定是存在而且确定的。虽然不知道,但没关系,因为当 $p=0.05$ 时,$q=c-0.1$,此时 $pq=0.05c-0.005$;而当 $p=\frac{1}{4}c$ 时,$pq=\frac{1}{8}c^2$。令</p><p>$$g=\frac{1}{8}c^2-\frac{1}{20}c+\frac{1}{200} = \frac{1}{8}\left(c-\frac{1}{5}\right)^2$$</p><p>显然,$g \geqslant 0$ 恒成立,当且仅当 $c=0.2$ 时 $g=0$。而人物的原始面板就已经保证了 $c=0.6$ ,搭配装备后只会使得 $c\geqslant 0.6$,所以 $\frac{1}{8}c^2 \geqslant 0.05c-0.005$ 。所以,当 $q=2p=\frac{1}{2}c$ 的时候,函数 $e$ 取得极大值。</p><p>另一种方法是,画个图看一下。用 Geogebra 画出这几个(隐)函数的图像,再把这个极值点标出来,就知道时最大值还是最小值了。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/理论/genshin-impact-critical/geogebra-figure.png"><p>图中蓝色的绿色的曲面代表了函数 $e$ ,蓝色的平面代表了约束条件,红色的曲线就是满足约束条件时 $e$ 的可能取值,黑色的点就是极值点。显然,这是一个最大值点。</p><p>由此,我们最终得到结论:<strong>在暴击暴伤总量 $c$ 不变的情况下,当暴击率与暴击伤害比值为 $1:2$ 的时候,伤害期望是最高的</strong>。</p><h1 id="暴击头还是爆伤头"><a href="#暴击头还是爆伤头" class="headerlink" title="暴击头还是爆伤头"></a>暴击头还是爆伤头</h1><p>当角色有暴击武器或者暴伤武器的时候,这不是问题。由于属性稀释,直接选择另一个属性的理之冠即可。但是当角色没有暴伤武器或者暴击武器的时候,例如迪卢克和狼的末路,那么理之冠是选择暴击头还是爆伤头呢?</p><p>我们先假设其他圣遗物副属性完全没有暴击暴伤的情况。</p><p>当选择暴击头时,$p=0.361$ ,$q=0.5$ ,$pq=0.1802$ 。</p><p>当选择暴伤头时,$p=0.05$ ,$q=1.122$ , $pq=0.0561$ 。</p><p>这样就一目了然了,优先选择暴击头。那么这是什么原因呢?</p><p>一种简单的理解是属性稀释。确实,暴伤也是会被稀释的。准确地说所有属性都会被稀释,而当属性提升前的值 $x_0$ 越高时,提升量 $x$ 的稀释越严重。因为提升度 $t$ 有</p><p>$$t = \frac{x}{x_0+x}, \frac{\partial t}{\partial x_0} = -\frac{x}{(x_0-x)^2}$$</p><p>所以 $t$ 随 $x_0$ 的增大而减小。暴伤基础值是 0.5,暴击基础值是 0.05 ,所以装备暴击头时提升幅度有8倍,但是带暴伤头时提升幅度只有2倍,所以最好带暴击头。</p><p>另一种理解,我们其实可以算出来消灭一只怪物所需要的平均攻击次数(这里简化一下)。因为怪物的血量 $m$ 是一定的,所需要的攻击次数 $n$ 就应该有</p><p>$$\begin{aligned}n&=\frac{m}{e}=\frac{m}{(1+p(c-2p))a} \\\frac{\partial n}{\partial p}&=-\frac{m}{e^2}(c-4p)a\end{aligned}$$</p><p>当 $p<\frac{1}{4}c$ 时,$n$ 递增,当 $p<\frac{1}{4}c$ 时,$n$ 递减。而只有理之冠的主属性有暴击暴伤时, $c$ 基本上是 1.222,所以其实 $p=0.3055$ 时,需要的攻击次数是最低的。而且,由于 $n$ 在大于0的部分是对称的,对称轴是 $\frac{1}{4}c$ ,所以装暴击头时 $p$ 的值离对称轴更近,所以总攻击次数更小。</p><p>对于<a href="https://bbs.mihoyo.com/ys/obc/content/75/detail?bbs_presentation_style=no_header" target="_blank" rel="noopener">迪卢克</a>这种突破加暴击的角色,满级时暴击率是 0.242,暴击暴伤总量 $c$ 达到 1.606 。装备暴击头时,$p=0.553$ ,与 $c/4$ 差距是 0.1515;装备暴伤头时,暴击率与 $c/4$ 差距是 0.1595。所以还是装备暴击头更好,当然差距不大,完全可以看副属性那个好带那个。</p><p>此外还有一点,迪卢克是一个爆发不是很离谱的角色。对于<a href="https://bbs.mihoyo.com/ys/obc/content/2040/detail?bbs_presentation_style=no_header" target="_blank" rel="noopener">优菈</a>这种爆发离谱的角色,暴击率带高一些可以少凹几次,减少痛苦。</p><p>当然对于<a href="https://bbs.mihoyo.com/ys/obc/content/1627/detail?bbs_presentation_style=no_header" target="_blank" rel="noopener">胡桃</a>这样,突破加暴伤,暴伤专武,就不用纠结了,最终还是配平暴击暴伤比,尽量 $p=2q$,并且让 $c$ 越高越好。</p>]]></content>
<summary type="html">
<p>原神中的圣遗物属性搭配是一个既烧摩拉又有趣的问题。相信很多玩家听说过一个结论,“角色面板暴击暴伤比为1:2时,期望伤害最高”。那为什么会得到这个结论呢?这涉及到很多有趣的数学问题,这篇博客中我们尝试进行推导。但是,由于实际角色伤害还取决于攻击力,但是引入攻击力的会使问题变得
</summary>
<category term="理论" scheme="http://hpdell.github.io/categories/%E7%90%86%E8%AE%BA/"/>
<category term="原神" scheme="http://hpdell.github.io/tags/%E5%8E%9F%E7%A5%9E/"/>
</entry>
<entry>
<title>自己动手写动态博客(二)</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/dynamic-blog-v2/"/>
<id>http://hpdell.github.io/编程/dynamic-blog-v2/</id>
<published>2022-03-20T20:34:46.000Z</published>
<updated>2022-04-14T16:50:55.429Z</updated>
<content type="html"><![CDATA[<h1 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h1><p>2019年,我写了一个动态博客,并发表了一篇博客《<a href="https://hpdell.github.io/%E7%BC%96%E7%A8%8B/dynamic-blog/">自己动手写动态博客</a>》,主要介绍了基于 Quasar 和 Express 的动态博客框架。但是后面这个动态博客被废弃了,原因是多方面的。一方面项目的部署没有基于 Docker,导致维护起来比较复杂。另一方面,该项目是前后端分离的,文章内容通过接口进行获取,再加上没有 SSR,所以就算想要提交搜索引擎,也很难进行搜索。此外,项目缺乏一个高可用性的后端管理平台。再加上当时网页设计水平有限,很多涉及其实比较反人类,因此最终还是继续使用了基于 GitHub Pages 的静态博客。</p><p>其实在做这个动态博客之前,并没有真的打算做一个博客出来,只是为了试一下 <a href="https://pypi.org/project/django-vditor/" target="_blank" rel="noopener">django-vditor</a> 这个包,甚至项目文件夹名称都是 <code>test-django-vditor</code>,因为 <a href="https://b3log.org/vditor/" target="_blank" rel="noopener">Vditor</a> 差不多已经是前端最强 Markdown 编辑器了。倒是初衷和博客有一定关系。2020年初设计了 GWmodel Lab 的<a href="http://gwmodel.whu.edu.cn/" target="_blank" rel="noopener">主页</a>,内容很全。由于网页是部署在群晖的 Web 服务器上,限制很大,所以项目虽然是前后端分离,但是总体上是一个静态网页,所有的 API 全都保存成了 JSON 文件。但是维护起来非常麻烦。这个主页一共分为了三个仓库:React 前端、Markdown 内容、Django 管理平台。当需要增加内容时,先在写好 Markdown 内容,使用 Git 进行版本管理,然后在 Django 中向数据库中添加相应的项目,再将接口响应内容保存成 JSON 文件,最后将 React 前端和内容以及接口相应内容部署到 Web 服务器上。当我还在实验室的时候,整个流程已经被打通,更新还算方便。但是交接给师弟的时候,想要讲清楚整个流程,就非常难了。</p><p>那么有没有一种比较方便的管理方法呢?有,其实只要找到一种方法,让 Django 直接可以输出整个网页,然后使用一些脚本将这些网页保存成 HTML 文件,再发布到网页服务器上就可以了。这个过程甚至可以让 Django 自己完成。配合 Django 的管理平台,更新内容就不复杂了。进一步地,如果要对搜索引擎友好,可以抛弃前后端分离的思路,反而使用 Django 的模板引擎渲染页面。</p><p>基于以上思路,我做了这个动态博客,就当作实验室主页的一个小 Demo。</p><h1 id="框架"><a href="#框架" class="headerlink" title="框架"></a>框架</h1><p>这个动态博客的框架非常简单,就是 Django + Bootstrap,没有 MVVM 框架,没有前后端分离,以至于写的时候仿佛回到了自己 2016 年用 Bootstrap 写网页的时代。</p><p>但是这个框架对于个人博客来讲,就显得很方便了。毕竟个人博客的项目复杂度不高,没有使用前端框架的必要,也没有前后端分离的必要,事实上,前后端分离反而会带来很多其他麻烦。而且 Django 现在的功能已经非常强大了,很多功能(例如图片上传、权限管理)只需要增加一些配置就可以解决,其管理平台更是可以让我们少些很多代码。经过几年的发展,Bootstrap 使用起来也很方便了,而且样式并不过时,也很简洁,很适合个人博客。</p><p>下面介绍一些具体的细节。</p><h2 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h2><p>似乎每一个教关系数据库的教程,都会用博客作为案例场景进行介绍。这是因为这个场景非常简单,但是涉及了一对多、多对多两种关系。通常,会有四张表:文章(Post)、分类(Category)、标签(Tag)和作者(Author)。本框架暂时省略了作者表,因为目前来说作者只有我一个人。数据库中主要的表和它们的关系如下图所示。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/dynamic-blog-v2/er-figure.jpeg"><p>其中几个类的具体情况是</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/dynamic-blog-v2/class-figure.jpeg"><p>可以说这真的是最精简版的博客数据库了。当然数据库中实际存在的还是有 Django 自带的权限表 Group 和 User。为了使用 Vditor,实际实现时还是要把 TextField 替换成 VditorTextField,才能在 Django Admin 中使用 Vditor 进行编辑。</p><h2 id="视图和模板"><a href="#视图和模板" class="headerlink" title="视图和模板"></a>视图和模板</h2><p>视图这部分没什么好说的,主要是以下几个视图:</p><ul><li>Post 的增删查改</li><li>User 的登入登出</li><li>Home</li></ul><p>登入登出功能主要是限制 Post 增删改的权限。但是如果不要求在前端进行文章操作,完全可以不要登入登出和 Post 的增删改操作(比较适合导出成静态页面)。</p><p>主页 Home 的渲染是根据 Profile 中关联的第一个 User 表中用户的记录。由于这个项目在使用 Docker 部署的时候,必然会创建一个超级用户,这个超级用户就是作为第一个用户存在的。Home 中最左侧的介绍以及中间的头像,就是从数据库中取出这个超级用户的相应记录进行渲染的。</p><p>模板几乎与视图一一对应,只不过为了避免写重复的代码,将模板进行了分解,使用 Django 提供的 <code>extends</code> 关键词进行模板扩展。主要继承关系如下所示。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/dynamic-blog-v2/view-inherit.jpeg"><p>日后还可以在此基础上添加其他的模块,例如相册等。</p><h1 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h1><p>该项目提供了 Dockerfile 用于构建 <a href="https://hub.docker.com/r/hpdell/myzone-django" target="_blank" rel="noopener">Docker 镜像</a>,可以直接使用 Docker 部署。事实上,该项目就是利用 VSCode 的远程开发功能在 Docker 容器里面开发的。同时仓库中也提供了一个 docker-compose 配置样例,可以直接部署。下图是在服务器上部署后的效果。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/dynamic-blog-v2/protainer.png"><p>相应的 docker-compose 配置如下:</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">'3.8'</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line"><span class="attr"> app:</span></span><br><span class="line"><span class="attr"> image:</span> <span class="string">hpdell/myzone-django:latest</span></span><br><span class="line"><span class="attr"> volumes:</span></span><br><span class="line"><span class="attr"> - upload-data:</span><span class="string">/code/uploads</span></span><br><span class="line"><span class="attr"> ports:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">"8080:8000"</span></span><br><span class="line"><span class="attr"> depends_on:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">db</span></span><br><span class="line"><span class="attr"> links:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">"db:db"</span></span><br><span class="line"><span class="attr"> restart:</span> <span class="string">unless-stopped</span></span><br><span class="line"><span class="attr"> environment:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">DJANGO_SUPERUSER_USERNAME=</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">DJANGO_SUPERUSER_EMAIL=</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">DJANGO_SUPERUSER_PASSWORD=</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">MYZONE_HOST=huyg.site</span></span><br><span class="line"></span><br><span class="line"><span class="attr"> db:</span></span><br><span class="line"><span class="attr"> image:</span> <span class="attr">postgres:latest</span></span><br><span class="line"><span class="attr"> restart:</span> <span class="string">unless-stopped</span></span><br><span class="line"><span class="attr"> volumes:</span></span><br><span class="line"><span class="attr"> - postgres-data:</span><span class="string">/var/lib/postgresql/data</span></span><br><span class="line"><span class="attr"> environment:</span></span><br><span class="line"><span class="attr"> POSTGRES_USER:</span></span><br><span class="line"><span class="attr"> POSTGRES_DB:</span></span><br><span class="line"><span class="attr"> POSTGRES_PASSWORD:</span></span><br><span class="line"></span><br><span class="line"><span class="attr">volumes:</span></span><br><span class="line"><span class="attr"> postgres-data:</span> <span class="literal">null</span></span><br><span class="line"><span class="attr"> upload-data:</span> <span class="literal">null</span></span><br></pre></td></tr></table></figure><p>注意配置中将 <code>uploads</code> 文件夹(上传的图片所保存的位置)永久性保留了下来,这样每次代码更新,直接拉取最新镜像并重新部署容器即可(在 Protainer 中甚至只需要点点鼠标)。</p><h1 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h1><p>整个博客开发时间,满打满算可能有四天,就已经实现了绝大多数功能(有一些在文章中没有介绍,比如本地化、移动端适配),相比之前那个动态博客,开发难度降低了很多,而且可扩展性也很强。这得益于 Django 的强大功能,以及最简化的项目框架,这样我觉得没有选择最火热的 Vue/React 和前后端分离框架是一个非常正确的决定。这样我想起一段往事。</p><blockquote><p>去年我在进行毕设答辩的时候,有一个评委老师问我这样一个问题:“你觉得模型是越复杂越好,还是越简单越好?”我给出的回答是:“模型的复杂度要与实际问题相匹配。”</p></blockquote><p>虽然我不知道这个回答有没有让老师满意,但在开发做项目时,我觉得也是同样的情况,用的技术栈也不是越强大越好,而是要和项目内容相匹配。用一些简化的技术去开发复杂的项目,难度一定会越来越大;而用一些太过强大的技术开发简单的项目,反而会带来很多不必要的麻烦。但是,这并不是说 Django 模板系统不强大,只是基于模板的渲染系统在项目规模更大时会变得比较麻烦,此时采用前后端分离的框架会更好。</p><p>当然,这个博客还有一些功能有待完善:</p><ul><li style="list-style: none"><input type="checkbox"> 草稿功能。思路是在 Post 中增加一个 Draft 字段来表示是不是草稿,前端再增加一个草稿箱的列表页面。</li><li style="list-style: none"><input type="checkbox"> 自动保存。思路是借助 Vditor 自带的缓存功能实现。</li><li style="list-style: none"><input type="checkbox"> 退出提醒。退出编辑页面时提示用户数据可能没有保存,避免文稿丢失。</li></ul><p>希望这个博客能够稳定运行更长的时间。</p>]]></content>
<summary type="html">
<h1 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h1><p>2019年,我写了一个动态博客,并发表了一篇博客《<a href="https://hpdell.github.io/%E7%BC%96%E
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="Django" scheme="http://hpdell.github.io/tags/Django/"/>
<category term="网页开发" scheme="http://hpdell.github.io/tags/%E7%BD%91%E9%A1%B5%E5%BC%80%E5%8F%91/"/>
<category term="Bootstrap" scheme="http://hpdell.github.io/tags/Bootstrap/"/>
</entry>
<entry>
<title>Windows 创建符号链接的命令 mklink</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/windows-cmd-mklink/"/>
<id>http://hpdell.github.io/编程/windows-cmd-mklink/</id>
<published>2022-01-19T13:03:12.000Z</published>
<updated>2022-04-14T16:50:55.581Z</updated>
<content type="html"><![CDATA[<p>在 Linux 中,命令 <code>ln</code> 可以方便地创建符号链接,而符号链接在系统运行过程中也起到了很重要的作用。符号链接,可以理解为也是一种文件或目录,有自己的名称,只不过访问这个文件或目录,等同于访问其目标。在 Windows 中,可以采用 <code>mklink</code> 命令创建符号链接,也能实现和在 Linux 中类似的效果。</p><h1 id="命令语法"><a href="#命令语法" class="headerlink" title="命令语法"></a>命令语法</h1><p>根据<a href="https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/mklink" target="_blank" rel="noopener">官方文档</a>,该命令的语法如下(注意这是一个 Windows Command Prompt 命令,不能在 Powershell 中使用):</p><figure class="highlight bat"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mklink [[/d] | [/h] | [/j]] <link> <target></span><br></pre></td></tr></table></figure><p>该命令中的两个参数 <code><link></code> 和 <code><target></code>, <code><link></code> 是符号链接的名称,而 <code><target></code> 是链接目标的路径。根据选项 <code>[/d] [/h] [/j]</code> 的不同该命令一共能创建四种符号链接:文件软链接、文件硬链接、目录软链接、目录联接。参考<a href="https://liam.page/2018/12/10/mklink-in-Windows/" target="_blank" rel="noopener">始终的博客</a>可知:</p><table><thead><tr><th></th><th>文件符号链接</th><th>文件硬链接</th><th>目录符号链接</th><th>目录联接</th></tr></thead><tbody><tr><td>选项</td><td>无</td><td><code>/h</code></td><td><code>/d</code></td><td><code>/j</code></td></tr><tr><td>参数</td><td>文件</td><td>文件</td><td>目录</td><td>目录</td></tr><tr><td>修改同步</td><td>是</td><td>是</td><td>是</td><td>是</td></tr><tr><td>删除同步</td><td>否</td><td>否</td><td>否</td><td>否</td></tr><tr><td>资源管理器类型</td><td><code>.symlink</code></td><td>无特殊显示</td><td>文件夹</td><td>文件夹</td></tr><tr><td>资源管理器图标</td><td>快捷方式</td><td>无特殊显示</td><td>文件夹快捷方式</td><td>文件夹快捷方式</td></tr><tr><td>彻底删除源</td><td>删除源路径</td><td>删除所有硬链接</td><td>删除源路径</td><td>删除源路径</td></tr></tbody></table><p>注意该命令运行需要管理员权限。</p><h1 id="目录符号连接和目录联接"><a href="#目录符号连接和目录联接" class="headerlink" title="目录符号连接和目录联接"></a>目录符号连接和目录联接</h1><p>对于文件,符号连接和硬链接的区别比较明显,而且和 Linux 中的差不多。但是对于目录,有符号链接和联接两种模式,这两种模式看起来是一样的,有什么区别?</p><p>根据 <a href="[Understanding NTFS Hard Links, Junctions and Symbolic Links (2brightsparks.com">Understanding NTFS Hard Links, Junctions and Symbolic Links</a>](<a href="https://www.2brightsparks.com/resources/articles/NTFS-Hard-Links-Junctions-and-Symbolic-Links.pdf)" target="_blank" rel="noopener">https://www.2brightsparks.com/resources/articles/NTFS-Hard-Links-Junctions-and-Symbolic-Links.pdf)</a>) 中的介绍,这两种模式最大的区别在于使用的技术不同:</p><ul><li>目录联接在 Windows 2000 中被引入,主要基于 NTFS 文件系统的 reparse points 特性实现。重定向的目标必须通过绝对路径定义,所有用于确定目标的信息都在这个路径里。而由于是基于 NTFS 文件系统实现的,所以目录联接只能用于本地路径。</li><li>目录符号链接在 Windows Vista 中被引入,是一种更高级的快捷方式。重定向的目标可以是本地路径,也可以是通过 SMB 协议挂在的网络路径。</li></ul><p>因此,当需要挂在网络路径时,就需要使用 <code>/d</code> 参数创建目录符号链接。如果只是本地路径,那么使用 <code>/j</code> 或者 <code>/d</code> 都可以。</p><p>但是,当链接到 OneDrive 中的文件夹时,如果使用符号链接,会造成一些问题。如果文件没有被下载到本地,那么在双击运行时,资源管理器在下载文件后会产生文件冲突的错误。此时就只能使用 <code>/j</code> 选项,创建目录联接,才可以正常使用。</p>]]></content>
<summary type="html">
<p>在 Linux 中,命令 <code>ln</code> 可以方便地创建符号链接,而符号链接在系统运行过程中也起到了很重要的作用。
符号链接,可以理解为也是一种文件或目录,有自己的名称,只不过访问这个文件或目录,等同于访问其目标。
在 Windows 中,可以采用 <cod
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="Windows" scheme="http://hpdell.github.io/tags/Windows/"/>
</entry>
<entry>
<title>《风起洛阳》观后感</title>
<link href="http://hpdell.github.io/%E9%9A%8F%E7%AC%94/wind-from-luoyang/"/>
<id>http://hpdell.github.io/随笔/wind-from-luoyang/</id>
<published>2021-12-31T23:30:44.000Z</published>
<updated>2022-04-14T16:50:55.581Z</updated>
<content type="html"><![CDATA[<p>在2022年到来之前终于把这个风起洛阳看完了。</p><p>总体而言,这个剧比较适合7左右的评分,就是总体上看着也不那么无聊,但是要说有多神,也并没有多神。可能最神的就是黄轩的演技。</p><p>纵观全剧,最大的一个问题在于剧中出场的人物太多,发生的事件太多,导致很多角色不免沦为工具,需要推动剧情的时候就出来一下,为剧情服务的痕迹太重。而有时候往往为了使一个人物的一个情节合理,就必须要先出现另一个人物,另一个情节,最终造成了一些逻辑上的不合理。比如柳公,似乎柳公在全剧就只有一个作用,就是让百里弘毅问他天通道人的手札;柳沣,似乎就是让他因为佩娘被杀而起意刺杀武攸决。这些大多数的不合理都被剧情跳进式的节奏掩盖了,但是这恰恰是本剧看了让人觉得一般的原因。最可惜的是七娘,整个角色在剧中一没有什么作用,二没什么成长,白白浪费了宋轶这个演员。</p><p>其实就像高秉烛,高秉烛一开始是为了给兄弟们报仇而活,当仇报了,他又需要什么样的目的去活?我觉得角色也是这样,编剧需要给登场的角色一个在剧中存在的合理的理由,他们的存在要有自己目的,而不是仅仅是推动剧情。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/随笔/wind-from-luoyang/meanings-of-being-alive.png"><p>另外本剧似乎不够重视告诉观众剧中人物各种行为的动机,因而给人一种观感,就是不知道主角三个人为什么要查案,为什么要拼了命查这个案。导演用了一些碎片化剪辑的手段,将主角之前的事逐渐交代,但是这会让人在了解全貌之前看得云里雾里。在不知道高秉烛的兄弟是怎么死之前,观众虽然知道高秉烛不是一个一般人,但是他为什么要干这些事?在柳襄死了之后,春秋道还没有完全展开之前,百里弘毅又是为了什么要继续查案?武思月身为内卫,在爱上高秉烛之前,为什么要拼死拼活去查一些好像也没必要她亲自去查的案件(当然你说武思月尽忠职守,倒也讲得通)。这个问题要说大也不大,反正无脑也能过去。</p><p>本剧还有一个问题,导演为了多线推进,大量使用了一种最简单的,这边剪一点,那边剪一点,以告诉观众两边人在同时干什么。这种剪辑用多了,就给人一种导演图省事的感觉,不去区分在每个事件中各个故事线的主次。唯一精彩的地方是武思月发现高秉烛进了联昉那段。当时是以武思月为主线,高秉烛已经有一两集没怎么出现了,这时观众会和武思月产生共情:高秉烛去哪了?然后突然高秉烛出现,观众和武思月同时恍然大悟。这就是一个精心设计线索的例子。但是其他很多时候,都是不分主次地多线并进。</p><p>之前还和女朋友吐槽过,这个剧的感情线比较灾难。首先是王一博和宋轶、黄轩和宋茜,实在是缺乏CP感。其次,百里弘毅和七娘的感情线,在前期很长一段时间没有什么成长,基本就是重复着百里弘毅外冷内热(甚至也看不到内热),七娘献尽殷勤,最可惜的是百里弘毅约工部监修一场戏,七娘突然闯入,百里弘毅想让她离开而说了几句重话,事后马车上说也有真话,本以为此处七娘会有一些心凉,但是事后发现并没有。这就仿佛七娘成为了衬托百里弘毅外冷内热的工具人。这二人直到最后才有所成长,来得实在是太慢了。至于高秉烛和武思月,本来找到天通道人后被困山洞,武思月负伤一场戏是二人感情线的一个重要事件,但是由于发生得太早,观众直到武思月不会死,本来的与死神赛跑失去了紧张刺激感。而且对角色形象的塑造并没有什么作用。就单纯的感情线塑造而言,还真不如最后一集武思月中箭,血从口中流出,回忆杀开始。</p><p>其他缺点还有很多。至于本剧好的地方,主要就是黄轩,黄轩对于角色在各个场景的把握实在是太到位了,他和任何角色对戏都尽显主角风范,时时刻刻都有一个主角的气场。然后是宋轶,对百里弘毅爱的表现淋漓尽致。只可惜宋轶好像不是很适合演女将,不然武思月让她演估计会更为出彩一些。还有本剧的场景精致,武戏精彩,可以看到至少前期是把钱花在制作上了。最后本剧节奏在大多数时间还是比较在线的,而且悬疑感营造的还不错,看起来至少不会无聊。当然优点也不只这些,我只是暂时能想起来这些。</p><p>还好,在2022年到来之前写完了这篇影评。</p>]]></content>
<summary type="html">
<p>在2022年到来之前终于把这个风起洛阳看完了。</p>
<p>总体而言,这个剧比较适合7左右的评分,就是总体上看着也不那么无聊,但是要说有多神,也并没有多神。可能最神的就是黄轩的演技。</p>
<p>纵观全剧,最大的一个问题在于剧中出场的人物太多,发生的事件太多,导致很多角
</summary>
<category term="随笔" scheme="http://hpdell.github.io/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="影评" scheme="http://hpdell.github.io/tags/%E5%BD%B1%E8%AF%84/"/>
</entry>
<entry>
<title>《一生一世》观后感</title>
<link href="http://hpdell.github.io/%E9%9A%8F%E7%AC%94/only-and-forever/"/>
<id>http://hpdell.github.io/随笔/only-and-forever/</id>
<published>2021-11-04T23:12:27.000Z</published>
<updated>2022-04-14T16:50:55.561Z</updated>
<content type="html"><![CDATA[<p>起初看这个剧,是因为女朋友看完了,跟我说这个偶像剧不一样,我就也想看一下,这样可以和她一起讨论。师姐强推《周生如故》,我没看《周生如故》直接看的《一生一世》。</p><p>先说说演员。让我感觉最意外的是,居然在演员表里面看到了龙斌和罗海琼。以前《第十放映室》还是龙叔配音的时候,就很喜欢龙叔的声音,没想到这次还能看到他演习。罗海琼是在《大宋提刑官》里面认识的,当时就很喜欢竹瑛姑。现在有快20年过去了,明显感觉她也老了。任嘉伦之前看过一集好像是他和阚清子还是谁演的侦探剧,其他就没看过了。白鹿本人我是第一次看她的电视剧,但是我总觉得她有一个角度特别像李沁。其他的,我感觉白鹿演配音演员的把式还不够像,任嘉伦说话叹气太多,这些都是不足之处。</p><p>整个剧的剧情看下来,导演的节奏安排还是有一些问题的。前20集几乎没有什么矛盾冲突,主要的矛盾在于周生辰、佟佳人、王曼、周文川、王英东等感情纠纷,以及时宜和秦婉的矛盾。周生辰和时宜本身根本谈不上矛盾纠纷,前半年通邮件就没有拍,后面认识、求婚以超乎常人的速度发展,虽然亲密举动比较少,但是糖不少,这反而是这个剧比较特别的地方。到了20集之后,矛盾冲突集中爆发,以至于让人觉得时宜每次去周生辰家就没什么好事发生。时宜落水、周文幸之死、周文川黑化、时宜昏迷。但总体看下来,除了周生辰和时宜感情线比较丰满,其他线略显单薄。周家内部的矛盾冲突既没有给人惊心动魄的感觉,也没有给人暗流涌动的感觉,导致比较乏味。</p><p>究其原因,我认为只有周文川一个反面角色,反派没有形成集团。即使周文幸、秦婉做出了一些伤害时宜的事,但最后都有解释,是出于好心。如果反派不够强大,则无法体现正派的强大,正反双方的矛盾冲突就难以建立。周文川最后的挣扎,不过也就是用水果刀绑架时宜,没有什么实现谋划,属于激情犯案。因此总体上,这不是智谋的争斗。因此导致这条线不够出彩。说实话,虽然周家的人物关系不一定能比得上荣国府宁国府,但是也不至于只掀起这些水花。</p><p>相比之下,感情线显得比较充分,量大管饱。其实在“前世”的设定下,机场偶遇、西安见面、镇江求婚,这个速度其实也可以理解了。虽然现实中这样的事确实不多,不过也有,刘涛就是个例子。虽然说她们这样谈恋爱的确不是一般人能实现的,一般人既没有任嘉伦白鹿的颜值,也没有周生辰、时宜的才能,更没有周家的家底。但是感情本身就是独一无二的,我也只需要看他们是怎么谈恋爱的就好了。</p><p>女朋友最感动的地方是时宜昏迷后,周生辰不受时宜父母待见,但是他坚持每天晚上坐在外面陪着,即使脸上有伤,胳膊骨折,直到最后终于被时宜父母接受,后面带着时宜去西安,每天跟她讲自己干了什么。看了之后我也非常感动。前面那么多集的铺垫,就让周生辰这个行为显得很合理,我们能相信他是会这样做的。</p><p>我自己还有一个很感动的地方,也许是因为我看偶像剧比较少吧。周生辰和时宜结婚后,周生辰去不来梅,两个人跨时区打电话。这正是我和我女朋友每天都在做的事。当初和女朋友认识,到在一起,就非常有缘分,在这个剧里也看到了一些我们之间的影子。</p><p>最后周生辰时宜结婚了,似乎这是他们从来没有怀疑过的事。我,我们,也是。</p><p>P.S. 我感觉小仁和圆圆很配。</p>]]></content>
<summary type="html">
<p>起初看这个剧,是因为女朋友看完了,跟我说这个偶像剧不一样,我就也想看一下,这样可以和她一起讨论。师姐强推《周生如故》,我没看《周生如故》直接看的《一生一世》。</p>
<p>先说说演员。让我感觉最意外的是,居然在演员表里面看到了龙斌和罗海琼。以前《第十放映室》还是龙叔配音的
</summary>
<category term="随笔" scheme="http://hpdell.github.io/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="影评" scheme="http://hpdell.github.io/tags/%E5%BD%B1%E8%AF%84/"/>
</entry>
<entry>
<title>游戏《原神》开服一周年有感</title>
<link href="http://hpdell.github.io/%E9%9A%8F%E7%AC%94/genshin-impact-1year/"/>
<id>http://hpdell.github.io/随笔/genshin-impact-1year/</id>
<published>2021-09-17T14:42:00.000Z</published>
<updated>2022-04-14T16:50:55.449Z</updated>
<content type="html"><![CDATA[<p>写于公测一周年之际。</p><p>等飞机闲来无事,回顾一下这一年玩原神的历程,以及一些热点问题的思考。</p><h1 id="回忆"><a href="#回忆" class="headerlink" title="回忆"></a>回忆</h1><p>手机端公测第一天,我就下载了原神,B服。但由于那天要参加雅思考试,没有深入体验。后来师弟提到他也在玩原神,但在官服,我就重新下了个官服,注册了新的账号。好在之前没有怎么玩。深入体验之后,游戏中的音乐、美术、台词等等都无比吸引我,这些已经被夸了很多了。</p><p>前期的时候,大世界阵容主要是皇女+重云超导普攻流,由于我当时还没玩懂,皇女没敢升级,圣遗物也没有刷,所以极为刮痧,伤害在150左右。后来又了班尼特,输出能力才大幅提升。随后就是甘雨池抽到了卢姥爷,简单强化了一下圣遗物,就已经可以去深境螺旋走一走了。凭借打深渊前6层的原石,我抽到了胡桃。</p><p>胡桃应该是我第一个明显感觉到“想要抽到”的角色,最直接的原因,还是那个“嗷”。在XP这个人问题上,倒不是很喜欢米哈游喜欢的元素(懂得都懂),尤其是高跟鞋,感觉用这个角色打架的时候很心疼。正巧,胡桃穿的不是高跟鞋,腿上也没有夸张的黑丝,而是一双带红色装饰的白色长袜,上身也是低调淡雅的黑色上衣,暴露皮肤的部分很少,后面双马尾尽显少女的可爱与灵动。而那把护摩枪,实在和胡桃本身太配了。于是当我看到同玩的同学氪金抽了护摩之后,我也实在忍不住氪了点钱,出了一把护摩和一把狼末,正好胡桃卢姥爷都有武器了。当然除了外观,玩胡桃传说任务的时候,她所展现出的独特的气质与思维,令我不禁赞叹,感觉是个聪明有趣的女孩子。当然胡桃强度很强,但当我刚抽到的时候只觉得容易暴毙,而且由于没有好的圣遗物,输出能力也一般,可以说胡桃是完全依靠形象吸引到我的。</p><p>接下来就是温迪,由于同学夸了很久温迪,我也没多想就抽了,正好卡池开了之后第三抽我就抽到了,没有太费功夫。虽然温迪这个小男孩的麻花辫和白袜子很有特点,但我只用来当个聚怪工具人而已。</p><p>然后是钟离,也是必抽七神之一。抽钟离之前,意外的出了公子,导致我不得不又氪了点钱抽钟离。生命套很好弄,史莱姆枪也多的是,很好培养。这两个神有一个很大的特点,就是几乎可以无缝嵌入到任何队伍中,而且他们不靠自身练度就可以提高队伍整体能力。</p><p>下一个想抽到的就是优菈,完全是因为她输出很高,而且不是火系输出!这点很重要,只有火c很多时候相当不舒服。</p><p>之后便是万叶,第一次抽的时候歪了七七。奇妙的是,我有一天看了马老师的动态,她说她打了深渊之后去抽万叶就抽到了,于是我也这样试了一下,居然在第20抽就抽到了,打破了温迪的记录(当时总第34抽抽到)。果然,不愧是叶天帝,用班尼特+万叶+钟离+迪卢克,迪卢克直接翻身,刀刀过万。甚至由于对群能力出众,有时候甚至比胡桃还快。果然没有抽错。</p><p>最后一个节目便是等神里。版本更新后第一时间抽,抽到琴团长。于是疯狂肝地图、开宝箱、做任务,最后又氪了30块钱,终于是抽到了神里。天目影打刀很配。神里最戳我xp的一点是从霰步状态出来时,马尾辫一甩的时候,有青春时期的少女感,很清纯的感觉。在叶天帝和讨龙的辅助下,神里现在也能刀刀近万,输出还是很不错的。刷冰套也很顺利,直接出了一个带爆伤的暴击头,完美符合要求。</p><p>9月更新之后,因为首充翻倍重置了,所以氪了点钱去抽雷神,但是歪了刻晴。听说胡桃要复刻,现在就不打算抽出雷神了,及时收手。</p><p>总体而言,我在玩原神的时候非常快乐,想自己探索就自己探索,想和朋友联机就和朋友联机,灵活性非常高。我也制作了一些原神相关视频。从游戏制作质量上讲,我对这款游戏是非常满意的。</p><h1 id="氪金抽卡"><a href="#氪金抽卡" class="headerlink" title="氪金抽卡"></a>氪金抽卡</h1><p>原神是一款氪金抽卡游戏,也许很多喜欢买断制游戏的玩家、甚至是抵制氪金抽卡游戏的玩家并不喜欢。而当这样一款游戏在全球27国登顶的时候,就产生了一个奇怪的现象——“环大陆好评圈”。当然,并不是说在港澳台以及国外,所有人都喜欢原神,在大陆就所有人都讨厌原神。但数据证明,确实有这种情况存在。国内只有Taptap平台对于原神(手游)的评价比较高,其他基本都在5分左右,刚公测的时候比现在还要糟糕。</p><p>但是,我们要知道,玩游戏玩的是游戏内容,而不是商业模式。氪金抽卡还是买断,都是游戏的商业模式而已,尤其对于国产游戏而言,过于强调其商业模式的类型,无异于揠苗助长。因为,一款制作很差的国产游戏并不能仅仅因为他是买断制,就奉若珍宝;如果你认同这个道理,是不是同样地,一款制作优良的国产游戏也不能仅仅因为他是氪金抽卡制,就万人唾骂。但如果我们真的这样做的话,同样也会消耗国人对于国产游戏的情怀,也会遏制国产游戏发展的潜力:如果广大国产游戏玩家,对于买断制是“出必买”,是不是也会被当成换皮游戏的韭菜割呢?</p><p>另一方面,很多人都说过,原神可以0氪。确实,原神完全可以0氪,国家队的强度已经很不错了。但我为什么要氪金?我想很多原神玩家氪金,是真心想得到这个角色,我为了我想要的东西花钱,有什么问题吗?换个角度,厂家出了一个我喜欢的产品,我看到后花钱买了,厂家有什么问题吗?真正存在问题的,是“逼氪”,是那种不靠氪金你已经完全无法继续的游戏(硬逼氪),或者不氪金造成的不良体验已经抵消了游戏内容的良好体验(软逼氪),这种良好体验包括情怀、音美、战斗等。</p><p>无论是硬逼氪还是软逼氪,为什么我认为其对游戏产业是不好的,因为这属于对用户的一种绑架。就好像如果你经常去一个理发店,工作人员给你推销他们家的产品,你不想买,于是你不买他们就总是给你最后一个剪,或者总是给你剪得不好看,或者总是对你态度很差。这时你怎么办?如果理发店多,你可以换一家理发店。但如果没有其他理发店呢,当然也可以不理发留迪卢克的发型(雾),也可以自己理发,或者就干脆买他家产品,反正用了不会有害,甚至自己还能开心。</p><p>但原神是这种情况吗?不是。首先原神不氪金不会被区别对待,也可能活动打不过但最重要的原石总是最简单的难度就能拿到的,其他的无非是一些材料,很容易就能获得,这就相当于你不买理发店的产品,他们只是在你理完发之后背后的汗毛不主动剃掉。而且新角色有试用,还有传说人物关卡可以试用,你可以体验之后再决定买不买。所以原神是氪金抽卡,但不逼氪。</p><p>商业模式是否能促进产品成功?我认为可以。如果小米不走互联网手机的路,也许他们卖不了那么多,价格也不会压到那么低。但商业模式是否能决定产品成功与否?我想也不总是。也许有靠营销成功的产品,但产品能否成功,最终还是由产品的品质决定的。如果想通过营销手段来掩盖产品质量的问题,终究是不会长远。</p><h1 id="抄袭风波"><a href="#抄袭风波" class="headerlink" title="抄袭风波"></a>抄袭风波</h1><p>原神公测之初,最大的黑点恐怕是“抄袭”。但如今看来,抄袭的指控恐怕已经不再成立了。且不说拥有东半球最强法务部的任天堂没有状告米哈游,而且其他一众“被抄袭”的公司也没有告米哈游,甚至原神还得了一种大奖;单说原神中所体现的中国文化和中式价值观,是哪个外国的游戏能做出来的吗?恐怕没有。这是神不似。</p><p>至于形似不似,就是具体看游戏制作上。美术上,风格相似就是抄袭吗?至少要在建模、贴图等找到极为相似的地方吧。音乐上,恐怕没人会说陈致逸老师的音乐是抄袭。动作上,如果有相似动作就是抄袭,恐怕是不是只有一款游戏可以有走路这个动作?</p><p>总的来说,抄袭指的不是创意,而已创意的实现。同样是做网络唤醒(Wake on LAN),GitHub上一搜一大堆,原理甚至都可能是相通的,但是那些代码都是抄袭的吗?新显卡的评测视频都是那么几个测试步骤,那评测视频都是互相抄袭的吗?</p><p>其实想想当时《黑神话:悟空》第一支演示视频出来的时候,就有抄袭《只狼:影逝二度》的声音;当《原神》出来的时候,就有抄袭《塞尔达传说:旷野之息》的声音。为什么都是日本游戏被“抄袭”?</p><h1 id="文化输出"><a href="#文化输出" class="headerlink" title="文化输出"></a>文化输出</h1><p>其实文化输出并不是文化类产品的义务,但文化产品天然具有文化输出的能力和效果,不论是有意的还是无意的。原神是否有做到文化输出?当然有。输出的是不是中国文化?是。</p><p>其实可以思考一个问题,迪士尼拍的《花木兰》,是中国的题材,体现的是中国文化吗?其实还是美式文化。那原神的蒙徳用欧洲题材,输出的就是欧洲文化吗?用的日本题材就输出的是日本文化吗?</p><p>文化是一个比较抽象的概念,不是一个具体的表象。原神出现了很多和风元素,这些不能说是日本文化。因为文化是融合在认识自然的世界观、为人处世的方法论中的,是一举一动的行为准则、一言一行的思维模式中体现的。中国几千年来人们的服饰、语言、礼制等变化了很多,但中国文化反而一直传承并逐渐沉淀,到现如今的社会主义核心价值观。这种文化是几个中国符号、几个华裔演员就能由美国人输出的吗?</p><p>相反,日本文化是米哈游能输出的吗?</p><h1 id="黎明时刻"><a href="#黎明时刻" class="headerlink" title="黎明时刻"></a>黎明时刻</h1><p>我认为原神是国产游戏的黎明时刻。</p><p>从游戏工业的角度,原神的出现和成功为游戏产业积累了经验、培养了人才。从玩家的角度,原神带给玩家良好的、完整的游戏体验,也没有如逼氪等降低游戏体验的部分。从国人的角度,原神做到了文化输出,让世界开始深入了解中国文化。</p><p>但另一方面,原神绝不代表国产游戏所能达到的颠峰,正如黎明后才是真正的日出。原神有没有问题,有很多问题,爬山穿模、数值平衡、体力限制、刷本无聊等等。正因为如此,只有原神是远远不够的。国产游戏需要多种创意、多种模式、多种思想、多种体验的全面开花,百花齐放百家争鸣。我们只能说,在国产游戏发展历史上,原神注定成为浓墨重彩的一笔。</p><p>最后,原神策划还是多学习多研究吧,进步空间很大。</p>]]></content>
<summary type="html">
<p>写于公测一周年之际。</p>
<p>等飞机闲来无事,回顾一下这一年玩原神的历程,以及一些热点问题的思考。</p>
<h1 id="回忆"><a href="#回忆" class="headerlink" title="回忆"></a>回忆</h1><p>手机端公测第一天,我
</summary>
<category term="随笔" scheme="http://hpdell.github.io/categories/%E9%9A%8F%E7%AC%94/"/>
<category term="原神" scheme="http://hpdell.github.io/tags/%E5%8E%9F%E7%A5%9E/"/>
</entry>
<entry>
<title>使用 GitLab Runner 为 R 包配置持续集成服务</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/RPackage-GitlabRunner/"/>
<id>http://hpdell.github.io/编程/RPackage-GitlabRunner/</id>
<published>2020-11-04T20:43:15.000Z</published>
<updated>2022-04-14T16:50:55.409Z</updated>
<content type="html"><![CDATA[<h1 id="主要目的"><a href="#主要目的" class="headerlink" title="主要目的"></a>主要目的</h1><p>众所周知,R 是一个跨平台统计软件,支持 Debian Ubuntu Fedora openSUSE Windows macOS 等多种平台。R 中提供了众多的软件包,以实现各种功能。这些包被托管在 CRAN 上。我们如果写了自己的软件包,也是要发布到 CRAN 上,但是其要求会特别严格,需要这些包在所有平台上编译通过。R 提供了一个命令 <code>R CMD check --as-cran</code> 以在本地实现模拟 CRAN 编译环境的测试命令。如果我们可以配置一个持续集成服务,在我们代码更新后直接在这些平台上进行测试,如果通过了就发布到 CRAN 上,不仅可以大大减少手动测试的工作量,也能尽早发现错误。</p><h1 id="平台选择"><a href="#平台选择" class="headerlink" title="平台选择"></a>平台选择</h1><p>但问题是,基于什么平台配置这个持续集成服务?</p><p>目前提供持续集成服务的平台非常多,例如 Travis, Jekins, Github, Gitlab …… 这些平台有的可以自己部署,有的只能使用公有服务;有的使用脚本配置,有的基于 Docker 。面对这么多平台,我们需要考虑到 R 和 R 包编译环境本身的特点:</p><ul><li>与其他软件相比,R 并不那么常用,所以几乎没有现成的 Docker 镜像可以使用;</li><li>检查 R 包所需要的依赖包可能非常多,光是 <code>R CMD check</code> 命令执行所需要的包就有十数个,编译环境的配置比较复杂;</li><li>R 包所需要测试通过的平台比较多,尤其涉及 Windows ,这些平台环境的配置方法也不甚相同。</li></ul><p>最后基本没得选,只剩下了 GitLab Runner ,几乎只有它能同时满足以上条件。而且我们之前也部署了一个 GitLab ,使用 GitLab Runner 顺理成章。</p><p>但是由于需要测试这么多平台,虽然我们有一个 ESXi 平台,但是创建虚拟机有点太奢侈和复杂了,毕竟 GitLab Runner 也就是个小软件。我们可以使用 Docker 容器替代虚拟机。使用 Docker 容器,我们只需要下载相应的镜像,创建容器,在容器中配置环境即可。由于缺乏经验,能力尚不足以直接构建好相应的 R 镜像,因此我们直接下载相应系统的官方镜像,然后在容器中直接配置环境。</p><p>综上,我们选定如下部署路线:</p><ol><li>下载相应系统的镜像,创建容器</li><li>在容器中安装 R 软件</li><li>在容器配置 R 包编译环境</li><li>在容器中安装 GitLab Runner 并进行注册</li><li>在 GitLab 仓库中编写持续集成配置文件</li></ol><p>根据以上路线,我们成功配置好了 5 个 GitLab Runner ,实现了在不同系统上的持续集成。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/RPackage-GitlabRunner/runner-pipeline.png"><h1 id="部署方法"><a href="#部署方法" class="headerlink" title="部署方法"></a>部署方法</h1><h2 id="创建容器"><a href="#创建容器" class="headerlink" title="创建容器"></a>创建容器</h2><p>下载镜像和创建容器就详解了,网上到处都是资料。而且由于我们是在群晖中部署的,所以基本只是点了点鼠标。命令行的方法我就暂时不介绍了。</p><p>所需要注意的是对于 Fedora 系统,为了方便配置 GitLab Runner ,启动脚本请使用<code>/sbin/init</code> 而不要使用 <code>/bin/bash</code> 以防系统无法启动服务,而且还要使用高级权限运行。</p><h2 id="安装-R-软件"><a href="#安装-R-软件" class="headerlink" title="安装 R 软件"></a>安装 R 软件</h2><p>首先需要安装 R 。在不同系统上安装 R 的方法,都写在 CRAN 中。这里总结一下 Linux 系统的安装方法,因为 Windows 和 macOS 有图形界面,安装很方便。</p><h3 id="Ubuntu"><a href="#Ubuntu" class="headerlink" title="Ubuntu"></a>Ubuntu</h3><p>修改 apt 源,将以下内容放到 <code>/etc/apt/sources.list</code> (或 <code>/etc/apt/sources.list.d</code> 目录下的文件)中</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">deb http://<CRAN地址>/bin/linux/ubuntu <系统版本代号>-<R版本号>/</span><br></pre></td></tr></table></figure><p>CRAN 地址可以使用官方地址,但是国内速度较慢,不建议使用。可以使用如下镜像地址:</p><ul><li>清华大学: <code>mirrors.tuna.tsinghua.edu.cn/CRAN</code></li><li>GWmodel : <code>gwmodel.whu.edu.cn/mirrors/CRAN</code> </li></ul><p>使用方法就是把后面的域名和路径替换 <code><CRAN地址></code> 的部分。</p><p>系统版本代号可以使用命令 <code>cat /etc/os-release</code> 查看,几个 LTS 版本的版号如下</p><table><thead><tr><th>版本号</th><th>系统版本代号</th></tr></thead><tbody><tr><td>20.04</td><td>focal</td></tr><tr><td>18.04</td><td>bionic</td></tr><tr><td>16.04</td><td>xenial</td></tr></tbody></table><p>R 版本号取决于要安装哪个 R 的版本</p><table><thead><tr><th>版本号</th><th>填写内容</th></tr></thead><tbody><tr><td>4.0</td><td>cran40</td></tr><tr><td>3.5, 3.6</td><td>cran35</td></tr></tbody></table><p>例如,如果想使用 GWmodel 的 CRAN 镜像地址,那么就使用如下 apt 源</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">deb http://gwmodel.whu.edu.cn/mirrors/CRAN/bin/linux/ubuntu bionic-cran40/</span><br></pre></td></tr></table></figure><p>然后,认证密钥,使用如下命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9</span><br></pre></td></tr></table></figure><p>之后使用 <code>apt update</code> 和 <code>apt install</code> 即可安装。</p><h3 id="Debian"><a href="#Debian" class="headerlink" title="Debian"></a>Debian</h3><p>安装方法总体和 Ubuntu 一样,但是系统版本代号不太一样,但是一样可以通过 <code>cat /etc/os-release</code> 查看</p><table><thead><tr><th>版本号</th><th>系统版本代号</th></tr></thead><tbody><tr><td>10</td><td>buster</td></tr><tr><td>9</td><td>stretch</td></tr><tr><td>8</td><td>jessie</td></tr></tbody></table><p>然后使用如下命令认证密钥</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-key adv --keyserver keys.gnupg.net --recv-key <span class="string">'E19F5F87128899B192B1A2C2AD5F960A256A04AF'</span></span><br></pre></td></tr></table></figure><p>之后同样使用 apt 安装即可。</p><h3 id="openSUSE"><a href="#openSUSE" class="headerlink" title="openSUSE"></a>openSUSE</h3><p>这个安装比较简单,但是我在使用 Leap 15.2 版本的时候总是会卡住,所以最好还是使用 Leap 15.1 或版本。安装方法是运行如下命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">VERSION=$(grep <span class="string">"^PRETTY_NAME"</span> /etc/os-release | tr <span class="string">" "</span> <span class="string">"_"</span> | sed -e <span class="string">'s/PRETTY_NAME=//'</span> | sed -e <span class="string">'s/"//g'</span>)</span><br><span class="line">zypper addrepo -f http://download.opensuse.org/repositories/devel\:/languages\:/R\:/patched/<span class="variable">$VERSION</span>/ R-base</span><br><span class="line">zypper install R-base=4.0.3</span><br></pre></td></tr></table></figure><p>指定版本号非常重要,不然会安装 R 3.5 版本。</p><h3 id="Fedora"><a href="#Fedora" class="headerlink" title="Fedora"></a>Fedora</h3><p>这个安装更加简单,官方库自带了 R ,所以直接使用如下命令安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dnf install R</span><br></pre></td></tr></table></figure><p>需要注意的是, Fedora 32 和 33 有 4.0 版本的 R ,之前的版本只有 3.6 版本的 R 。</p><h2 id="配置-R-包编译环境"><a href="#配置-R-包编译环境" class="headerlink" title="配置 R 包编译环境"></a>配置 R 包编译环境</h2><p>主要就是安装依赖包了,这个就和要进行持续集成的 R 包本身相关了。可以直接运行一次 <code>R CMD check</code> 命令查看有哪些依赖包没有安装。</p><p>安装包推荐设置 CRAN 镜像,这时的设置方法是将如下语句放在 Rprofile 文件中(根据使用的镜像不同选择不同的地址)</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 清华大学 CRAN 镜像</span></span><br><span class="line">options(<span class="string">"repos"</span> = c(CRAN=<span class="string">"https://mirrors.tuna.tsinghua.edu.cn/CRAN/"</span>))</span><br><span class="line"><span class="comment"># GWmodel CRAN 镜像</span></span><br><span class="line">options(<span class="string">"repos"</span> = c(CRAN=<span class="string">"http://gwmodel.whu.edu.cn/mirrors/CRAN/"</span>))</span><br></pre></td></tr></table></figure><p>如果你的包依赖了 <code>sf</code> 包,那么在各个系统下需要安装如下依赖库(不是在 R 中安装)</p><ul><li>Ubuntu & Debian : libgdal-dev, libudunits2-dev</li><li>Fedora: gdal-devel, libproj-devel, geos-devel, udunits2-devel, sqlite-devel</li><li>openSUSE: gdal-devel, libproj-devel, geos-devel, gdal, proj 然后 udunits2 手动编译安装</li></ul><p>其他依赖库也都是通过包管理器安装,这样 R 包的依赖才能通过。</p><h2 id="安装-GitLab-Runner-并进行注册"><a href="#安装-GitLab-Runner-并进行注册" class="headerlink" title="安装 GitLab Runner 并进行注册"></a>安装 GitLab Runner 并进行注册</h2><p>Windows 和 macOS 系统就不说了,非常简单。Ubuntu 和 Debian 上也有官方教程,直接可以照着做。Fedora 和 openSUSE 就会遇到奇奇怪怪的问题,尤其是运行 <code>gitlab-runner install</code> 会报系统不支持。这里主要总结一下 Linux 系统的。</p><h3 id="Ubuntu-和-Debian-上的安装"><a href="#Ubuntu-和-Debian-上的安装" class="headerlink" title="Ubuntu 和 Debian 上的安装"></a>Ubuntu 和 Debian 上的安装</h3><p>Ubuntu, Debian 可以参考<a href="[gitlab](https://docs.gitlab.com/runner/install/">官方文档</a>)进行安装,推荐使用包管理方式。如果有依赖问题解决以来问题即可。</p><h3 id="Fedora-32-上的安装"><a href="#Fedora-32-上的安装" class="headerlink" title="Fedora 32 上的安装"></a>Fedora 32 上的安装</h3><p>之所以说 Fedora 32 ,因为官方没有明确支持这个版本,支持 Fedora 30 ,但是 Fedora 30 并没有 R 4.0 的包。但是经过实测, Fedora 32 确实可以安装 GitLab Runner 只是要解决一些问题。所以这个方法只保证这个系统有效,其他版本并不保证。</p><p>安装前,容器中可能没有 service 命令,可以通过安装 <code>initscripts</code> 命令进行解决。此外建议安装 <code>lsb</code> 包。</p><p>还记得一开始说容器的启动脚本要使用 <code>/sbin/init</code> 吗?这使得容易可以启动服务。如果使用 <code>/bin/bash</code> 启动的话,即使装了 service 命令,也可能无法启动服务。</p><p>然后使用官方软件包进行安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | bash</span><br><span class="line"><span class="built_in">export</span> GITLAB_RUNNER_DISABLE_SKEL=<span class="literal">true</span></span><br><span class="line">dnf install gitlab-runner</span><br><span class="line">gitlab-runner install</span><br></pre></td></tr></table></figure><p>这样应该就安装好了。</p><h3 id="openSUSE-Leap-15-1-上的安装"><a href="#openSUSE-Leap-15-1-上的安装" class="headerlink" title="openSUSE Leap 15.1 上的安装"></a>openSUSE Leap 15.1 上的安装</h3><p>这个明确的讲,使用的是奇技淫巧,不能保证永远可行。</p><p>首先下载 gitlab-runner 二进制文件并放到 <code>PATH</code> 路径中(架构根据实际情况调整)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo curl -L --output /usr/<span class="built_in">local</span>/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64</span><br></pre></td></tr></table></figure><p>然后安装 <code>initscripts</code> 包,并准备以下文件,可以从 Ubuntu 中复制</p><ul><li><code>/etc/init.d/gitlab-runner</code></li><li><code>/lib/lsb/init-functions</code></li></ul><p>然后建立 <code>/etc/rc.d/rc0.d</code> 文件夹,在其中建立软链接</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ln -s /etc/init.d/gitlab-runner /etc/rc.d/rc0.d/K20gitlab-runner</span><br></pre></td></tr></table></figure><p>然后,直接运行 <code>gitlab-runner start</code> 应该就可以启动了。</p><blockquote><p>其实,<code>gitlab-runner install</code> 命令只是创建以下服务,这个过程手动创建也是可以的。上面就是模拟了手动创建的过程。如果你水平比较高,可以自己写服务启动脚本,就不需要从 Ubuntu 复制了。</p></blockquote><h3 id="GitLab-Runner-的注册"><a href="#GitLab-Runner-的注册" class="headerlink" title="GitLab Runner 的注册"></a>GitLab Runner 的注册</h3><p>注册非常简单,在所有系统上都是一样的,使用如下命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gitlab-runner register</span><br></pre></td></tr></table></figure><p>然后程序会问几个问题</p><ol><li>GitLab 网址</li><li>Runner 注册的 Token</li><li>Runner 的名字</li><li>Runner 的标签</li><li>Runner 的运行方式(这里选择 shell )</li></ol><p>以上问题可以根据<a href="https://docs.gitlab.com/runner/register/" target="_blank" rel="noopener">官方文档</a>进行填写,网上资料也比较多。</p><p>注册完,就可以使用如下命令启动了</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gitlab-runner start</span><br></pre></td></tr></table></figure><h2 id="在-GitLab-仓库中编写持续集成配置文件"><a href="#在-GitLab-仓库中编写持续集成配置文件" class="headerlink" title="在 GitLab 仓库中编写持续集成配置文件"></a>在 GitLab 仓库中编写持续集成配置文件</h2><p>如何编写持续集成配置文件,是个非常复杂的问题。这篇博客就不详细讲解了。这里贴出来我们写好的文件,然后提几个注意事项</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">stages:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">build</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">test</span></span><br><span class="line"></span><br><span class="line"><span class="attr">variables:</span></span><br><span class="line"><span class="attr"> GWMODEL_VERSION:</span> <span class="number">2.2</span><span class="bullet">-0</span></span><br><span class="line"></span><br><span class="line"><span class="attr">build:</span></span><br><span class="line"><span class="attr"> stage:</span> <span class="string">build</span></span><br><span class="line"><span class="attr"> tags:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">GWmodel</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">Ubuntu</span></span><br><span class="line"><span class="attr"> only:</span></span><br><span class="line"><span class="attr"> refs:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">master</span></span><br><span class="line"><span class="attr"> script:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">R</span> <span class="string">CMD</span> <span class="string">build</span> <span class="string">GWmodel</span></span><br><span class="line"><span class="attr"> artifacts:</span></span><br><span class="line"><span class="attr"> paths:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">GWmodel_$GWMODEL_VERSION.tar.gz</span></span><br><span class="line"></span><br><span class="line"><span class="attr">test_cran_ubuntu:</span></span><br><span class="line"><span class="attr"> stage:</span> <span class="string">test</span></span><br><span class="line"><span class="attr"> tags:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">GWmodel</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">Ubuntu</span></span><br><span class="line"><span class="attr"> only:</span></span><br><span class="line"><span class="attr"> refs:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">master</span></span><br><span class="line"><span class="attr"> script:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">R</span> <span class="string">CMD</span> <span class="string">check</span> <span class="string">GWmodel_$GWMODEL_VERSION.tar.gz</span> <span class="bullet">--as-cran</span></span><br><span class="line"><span class="attr"> dependencies:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">build</span></span><br><span class="line"></span><br><span class="line"><span class="attr">test_cran_debian:</span></span><br><span class="line"><span class="attr"> stage:</span> <span class="string">test</span></span><br><span class="line"><span class="attr"> tags:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">GWmodel</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">Debian</span></span><br><span class="line"><span class="attr"> only:</span></span><br><span class="line"><span class="attr"> refs:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">master</span></span><br><span class="line"><span class="attr"> script:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">R</span> <span class="string">CMD</span> <span class="string">check</span> <span class="string">GWmodel_$GWMODEL_VERSION.tar.gz</span> <span class="bullet">--as-cran</span></span><br><span class="line"><span class="attr"> dependencies:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">build</span></span><br><span class="line"></span><br><span class="line"><span class="attr">test_cran_openSUSE:</span></span><br><span class="line"><span class="attr"> stage:</span> <span class="string">test</span></span><br><span class="line"><span class="attr"> tags:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">GWmodel</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">openSUSE</span></span><br><span class="line"><span class="attr"> only:</span></span><br><span class="line"><span class="attr"> refs:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">master</span></span><br><span class="line"><span class="attr"> script:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">R</span> <span class="string">CMD</span> <span class="string">check</span> <span class="string">GWmodel_$GWMODEL_VERSION.tar.gz</span> <span class="bullet">--as-cran</span></span><br><span class="line"><span class="attr"> dependencies:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">build</span></span><br><span class="line"></span><br><span class="line"><span class="attr">test_cran_Fedora:</span></span><br><span class="line"><span class="attr"> stage:</span> <span class="string">test</span></span><br><span class="line"><span class="attr"> tags:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">GWmodel</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">Fedora</span></span><br><span class="line"><span class="attr"> only:</span></span><br><span class="line"><span class="attr"> refs:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">master</span></span><br><span class="line"><span class="attr"> script:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">R</span> <span class="string">CMD</span> <span class="string">check</span> <span class="string">GWmodel_$GWMODEL_VERSION.tar.gz</span> <span class="bullet">--as-cran</span></span><br><span class="line"><span class="attr"> dependencies:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">build</span></span><br><span class="line"></span><br><span class="line"><span class="attr">test_cran_windows:</span></span><br><span class="line"><span class="attr"> stage:</span> <span class="string">test</span></span><br><span class="line"><span class="attr"> tags:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">GWmodel</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">Windows</span></span><br><span class="line"><span class="attr"> only:</span></span><br><span class="line"><span class="attr"> refs:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">master</span></span><br><span class="line"><span class="attr"> script:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">R.exe</span> <span class="string">CMD</span> <span class="string">check</span> <span class="string">GWmodel_$GWMODEL_VERSION.tar.gz</span> <span class="bullet">--as-cran</span> <span class="bullet">--no-manual</span></span><br><span class="line"><span class="attr"> dependencies:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">build</span></span><br></pre></td></tr></table></figure><h3 id="Stages-和任务"><a href="#Stages-和任务" class="headerlink" title="Stages 和任务"></a>Stages 和任务</h3><p>yml 文件第一级的标签名,默认为是任务名,除非是一些特别的关键字,如 <code>stages</code> 。这里面标识了不同任务所处的阶段,按顺序排列,名字随意。只有一个阶段的任务全部通过,才执行下一个阶段的任务。这里第一个阶段 <code>build</code> 构建一个源码包。这个阶段就无需分系统进行,只有测试才需要分系统进行。</p><h3 id="任务中的-tags"><a href="#任务中的-tags" class="headerlink" title="任务中的 tags"></a>任务中的 tags</h3><p>一个任务执行只使用一个 Runner ,具体使用哪个 Runner ,就是通过 tags 选择的。所以这里的 tags 要和创建 Runner 时设置的 tags 相对应。</p><h3 id="任务中的-artifacts-和-dependencies"><a href="#任务中的-artifacts-和-dependencies" class="headerlink" title="任务中的 artifacts 和 dependencies"></a>任务中的 artifacts 和 dependencies</h3><p>如果一个任务想留下一些东西给后续任务使用,就需要使用 <code>artifacts</code> 指定留下哪些东西。这些东西也可以在 GitLab 上进行下载。这里我留下了 build 命令生成的压缩包,供后续 check 过程使用。</p><p>如果一个任务想使用之前任务留下来的东西,就需要使用 <code>dependencies</code> 指定获取哪些任务遗留下来的文件。因此,所有执行 <code>test</code> 阶段的任务都依赖于 <code>build</code> 那个任务即可。</p><blockquote><p>其实最后还应该有一步部署,理论上可以直接上传到 CRAN 中。但是这个还没研究出来怎么做,先挖个坑。</p></blockquote><blockquote><p>使用 <code>R CMD check --as-cran</code> 会尝试编译文档,因此需要 latex 。这个最好还是安装以下。如果不想安装,可以仿照 <code>test_cran_windows</code> 中添加 <code>--no-manual</code> 参数,就不会编译 pdf 版的文档了。</p></blockquote><hr><p>以上就是使用 GitLab Runner 进行 R 包持续集成的所有配置方法。如有不详细的地方,还请在评论区指出。</p>]]></content>
<summary type="html">
<h1 id="主要目的"><a href="#主要目的" class="headerlink" title="主要目的"></a>主要目的</h1><p>众所周知,R 是一个跨平台统计软件,支持 Debian Ubuntu Fedora openSUSE Windows mac
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="R" scheme="http://hpdell.github.io/tags/R/"/>
<category term="GitLab Runner" scheme="http://hpdell.github.io/tags/GitLab-Runner/"/>
</entry>
<entry>
<title>QGIS 二次开发笔记(3)——空间距离和空间权重</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/qgisdev3-spatialweight/"/>
<id>http://hpdell.github.io/编程/qgisdev3-spatialweight/</id>
<published>2020-08-12T17:50:31.000Z</published>
<updated>2022-04-14T16:50:55.573Z</updated>
<content type="html"><![CDATA[<!-- #! https://zhuanlan.zhihu.com/p/181694220 --><p>这个博客如果只是一个复刻 QGIS 的教程的话,就没有什么价值,大家只要照着 QGIS 中的复制粘贴就可以了。所以这篇博客先来介绍一些 QGIS 中没有的。我们使用 QGIS 做二次开发的目的,无非就是在软件中集成我们研发的一些算法,尤其是空间算法,不论是针对矢量数据还是栅格数据(我们主要研究矢量数据)。对于矢量数据的空间算法而言,空间距离和空间权重非常重要,因为其反映了地理学第一定律:</p><blockquote><p>任何事物都是与其他事物相关的,只不过相近的事物关联更紧密。</p></blockquote><p>我们可以看到,不论是在莫兰指数(Moran’s I)中,还是空间自回归模型(SAR)中,或者地理加权回归模型(GWR)中,空间权重都是非常重要的。</p><h1 id="空间权重"><a href="#空间权重" class="headerlink" title="空间权重"></a>空间权重</h1><p>空间权重的计算可以是多种多样的,除了有一条总的原则:权重随距离的增加而减小,即权重是距离的单调减函数。该函数可称为“空间权重核函数”,即 $w(d)$。该函数还可以引入一些参数,如 $w(d;b)$ ,参数 $b$ 可以事先指定,或者根据优化算法进行优化。</p><p>在莫兰指数、空间自回归模型等算法中,常常使用不含参数的空间权重函数,如“平方反距离函数”(忽略 $d=0$ 的情况)$$ w = \frac{1}{d^2} $$或者“指数反距离函数”$$ w = e^{-d} $$由于权重只有相对大小有意义,因此这些函数可以不用像概率密度函数一样要求 $\int w \ \mathrm{d}d = 1$ 。</p><p>在核密度分析、地理加权回归分析等算法中,常常使用含有一个参数 $b$ 的空间权重函数,该参数被称之为“带宽”(bandwidth)。例如常用的高斯权重核函数$$ w(d;b) = exp\left{ -\frac{1}{2} \left(\frac{d}{b}\right)^2 \right} $$该函数在任何距离上都有一定的权重,即使权重非常小,被称之为“非截断型”权重核函数。还有一种权重核函数,如“双平方权重核函数”$$ w(d;b) = \left{ \begin{matrix} \left( 1 - \left( \frac{d}{b} \right)^2 \right)^2 & d \leq b \ 0 & d> b \end{matrix} \right. $$即超过带宽范围的位置上权重均为 $0$ ,被称之为“截断型”权重核函数。</p><h1 id="空间距离"><a href="#空间距离" class="headerlink" title="空间距离"></a>空间距离</h1><p>空间权重是根据距离计算的,而如何定义距离,也是个非常重要的问题。我们最常用的就是欧氏距离(投影坐标系)或大圆距离(地理坐标系)。除此之外,还有其他一些会用到的距离计算方法。</p><p>地理加权回归分析中,也经常用到以下几个距离:</p><ol><li>曼哈顿距离:$D_{1,2}=|x_1-x_2|+|y_1-y_2|$</li><li>闵可夫斯基距离:$D_{1,2}=\left(|x_1-x_2|^p+|y_1-y_2|^p\right)^{\frac{1}{p}}$</li><li>路网距离:道路网络上两点的最短距离</li></ol><p>对于栅格数据,或者栅格采样的点数据,也可以采用“四邻域距离”、“皇后距离”等等。</p><p>时空地理加权回归分析中,采用了一个“时空距离”的概念,即将时间和空间组合到一起。原作者提供的思路是$$ D_{1,2} = \mu((x_1-x_2)^2+(y_1-y_2)^2)+\lambda(t_1-t_2)^2 $$其中 $\mu+\lambda=1$ 且 $\mu,\lambda>0$ ,并选择合适的值以平衡时间和空间因素。</p><p>如果数据是线数据,那又该如何定义距离呢?目前有几种定义 Flow 距离的方式,但是总体上不是很令人满意。而距离又需要满足非负性、同一性、对称性和三角不等式,因此往往需要根据实际研究内容的特点设计距离。</p><h1 id="空间权重和距离的程序实现"><a href="#空间权重和距离的程序实现" class="headerlink" title="空间权重和距离的程序实现"></a>空间权重和距离的程序实现</h1><p>根据以上介绍可以发现,空间权重离不开距离,各自又都多种多样,而且独立于算法。在面向对象语言中,我们可以使用继承和多态特性,实现对空间权重和距离的封装:</p><ol><li>将“空间权重”、“空间距离”分别定义为基类,再从其中派生出各种具体的权重和距离。</li><li>根据需要,将权重和距离进行组合,提供统一接口计算权重。</li></ol><p>空间权重的声明如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">QgsdkWeight</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="keyword">enum</span> WeightType</span><br><span class="line"> {</span><br><span class="line"> BandwidthWeight</span><br><span class="line"> };</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> QgsdkWeight() {}</span><br><span class="line"> <span class="keyword">virtual</span> ~QgsdkWeight() {}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> QgsdkWeight* <span class="title">clone</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="comment">// 求权重向量</span></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> vec <span class="title">weight</span><span class="params">(vec dist)</span> </span>= <span class="number">0</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>空间距离的声明如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">QgsdkDistance</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="keyword">enum</span> DistanceType</span><br><span class="line"> {</span><br><span class="line"> CRSDistance,</span><br><span class="line"> MinkwoskiDistance,</span><br><span class="line"> DMatDistance</span><br><span class="line"> };</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> explicit QgsdkDistance(int total) : mTotal(total) {};</span><br><span class="line"> QgsdkDistance(<span class="keyword">const</span> QgsdkDistance& d) { mTotal = d.mTotal; };</span><br><span class="line"> <span class="keyword">virtual</span> ~QgsdkDistance() {};</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> QgsdkDistance* <span class="title">clone</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> DistanceType <span class="title">type</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">total</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">setTotal</span><span class="params">(<span class="keyword">int</span> total)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="comment">// 计算距离的函数</span></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> vec <span class="title">distance</span><span class="params">(<span class="keyword">int</span> focus)</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 返回结果的元素个数</span></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="keyword">int</span> <span class="title">length</span><span class="params">()</span> <span class="keyword">const</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 求最大距离</span></span><br><span class="line"> <span class="function"><span class="keyword">double</span> <span class="title">maxDistance</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="comment">// 求最小距离</span></span><br><span class="line"> <span class="function"><span class="keyword">double</span> <span class="title">minDistance</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> <span class="keyword">int</span> mTotal = <span class="number">0</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><blockquote><p>这里只是用一个 <code>focus</code> 整型变量来指定计算当前数据中第 $i$ 个点到其他所有点的距离。在该设计下,需要将所有用于计算的数据保存在 <code>QgsdkDistance</code> 实例中,这是为了避免不同派生类所需参数不同的问题。但也可以采用另一种方法,即接受一个 <code>void *</code> 类型的参数,这个参数指向计算所需要的所有数据,即可实现不同类型参数的传递。或者也可以使用 Qt 中特有的 <code>QVariant</code> 类型,以在派生类中实现不同类型参数的传递。</p></blockquote><p>然后可以构建一个组合类,将权重和距离组合起来:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">QgsdkSpatialWeight</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> QgsdkSpatialWeight();</span><br><span class="line"> QgsdkSpatialWeight(QgsdkWeight* weight, QgsdkDistance* distance);</span><br><span class="line"> QgsdkSpatialWeight(<span class="keyword">const</span> QgsdkSpatialWeight& spatialWeight);</span><br><span class="line"> ~QgsdkSpatialWeight();</span><br><span class="line"></span><br><span class="line"> <span class="function">QgsdkWeight *<span class="title">weight</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">setWeight</span><span class="params">(QgsdkWeight *weight)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">setWeight</span><span class="params">(QgsdkWeight& weight)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function">QgsdkDistance *<span class="title">distance</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">setDistance</span><span class="params">(QgsdkDistance *distance)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">setDistance</span><span class="params">(QgsdkDistance& distance)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> QgsdkSpatialWeight& <span class="keyword">operator</span>=(<span class="keyword">const</span> QgsdkSpatialWeight& spatialWeight);</span><br><span class="line"> QgsdkSpatialWeight& <span class="keyword">operator</span>=(<span class="keyword">const</span> QgsdkSpatialWeight&& spatialWeight);</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> vec <span class="title">weightVector</span><span class="params">(<span class="keyword">int</span> i)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">isValid</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> QgsdkWeight* mWeight = <span class="literal">nullptr</span>;</span><br><span class="line"> QgsdkDistance* mDistance = <span class="literal">nullptr</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>然后分别对 <code>QgsdkWeight</code> 和 <code>QgsdkDistance</code> 进行具体实现,如带宽权重和欧式/大圆距离(隐去 get/set 函数):</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">QgsdkBandwidthWeight</span> :</span> <span class="keyword">public</span> QgsdkWeight</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">typedef</span> <span class="title">double</span> <span class="params">(*KernelFunction)</span><span class="params">(<span class="keyword">double</span>, <span class="keyword">double</span>)</span></span>;</span><br><span class="line"> <span class="keyword">static</span> KernelFunction Kernel[];</span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">double</span> <span class="title">GaussianKernelFunction</span><span class="params">(<span class="keyword">double</span> dist, <span class="keyword">double</span> bw)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">double</span> <span class="title">ExponentialKernelFunction</span><span class="params">(<span class="keyword">double</span> dist, <span class="keyword">double</span> bw)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">double</span> <span class="title">BisquareKernelFunction</span><span class="params">(<span class="keyword">double</span> dist, <span class="keyword">double</span> bw)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">double</span> <span class="title">TricubeKernelFunction</span><span class="params">(<span class="keyword">double</span> dist, <span class="keyword">double</span> bw)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">double</span> <span class="title">BoxcarKernelFunction</span><span class="params">(<span class="keyword">double</span> dist, <span class="keyword">double</span> bw)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> QgsdkBandwidthWeight();</span><br><span class="line"> QgsdkBandwidthWeight(<span class="keyword">double</span> size, <span class="keyword">bool</span> adaptive, KernelFunctionType kernel);</span><br><span class="line"> QgsdkBandwidthWeight(<span class="keyword">const</span> QgsdkBandwidthWeight& bandwidthWeight);</span><br><span class="line"> QgsdkBandwidthWeight(<span class="keyword">const</span> QgsdkBandwidthWeight* bandwidthWeight);</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> QgsdkWeight * <span class="title">clone</span><span class="params">()</span> override</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> vec <span class="title">weight</span><span class="params">(vec dist)</span> override</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="keyword">double</span> mBandwidth;</span><br><span class="line"> <span class="keyword">bool</span> mAdaptive;</span><br><span class="line"> KernelFunctionType mKernel;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">QgsdkCRSDistance</span> :</span> <span class="keyword">public</span> QgsdkDistance</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">static</span> vec <span class="title">SpatialDistance</span><span class="params">(<span class="keyword">const</span> rowvec& out_loc, <span class="keyword">const</span> mat& in_locs)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">static</span> vec <span class="title">EuclideanDistance</span><span class="params">(<span class="keyword">const</span> rowvec& out_loc, <span class="keyword">const</span> mat& in_locs)</span></span>;</span><br><span class="line"> <span class="function"><span class="keyword">static</span> <span class="keyword">double</span> <span class="title">SpGcdist</span><span class="params">(<span class="keyword">double</span> lon1, <span class="keyword">double</span> lon2, <span class="keyword">double</span> lat1, <span class="keyword">double</span> lat2)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">QgsdkCRSDistance</span><span class="params">(<span class="keyword">int</span> total, <span class="keyword">bool</span> isGeographic)</span></span>;</span><br><span class="line"> QgsdkCRSDistance(<span class="keyword">const</span> QgsdkCRSDistance& distance);</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> QgsdkDistance * <span class="title">clone</span><span class="params">()</span> override</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function">DistanceType <span class="title">type</span><span class="params">()</span> override </span>{ <span class="keyword">return</span> DistanceType::CRSDistance; }</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> vec <span class="title">distance</span><span class="params">(<span class="keyword">int</span> focus)</span> override</span>;</span><br><span class="line"> <span class="function"><span class="keyword">int</span> <span class="title">length</span><span class="params">()</span> <span class="keyword">const</span> override</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> <span class="keyword">bool</span> mGeographic = <span class="literal">false</span>;</span><br><span class="line"> mat* mFocusPoints = <span class="literal">nullptr</span>;</span><br><span class="line"> mat* mDataPoints = <span class="literal">nullptr</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>按照当前设计,这个 <code>QgsdkCRSDistance</code> 在使用的时候,就需要预先设置 <code>mFocusPoints</code> 和 <code>mFocusPoints</code> 属性。如果指定 <code>mGeographic</code> 为 <code>true</code> 则按照大圆距离计算,反之按照欧氏距离计算。这个 <code>QgsdkBandwidthWeight</code> 可以根据指定的不同的核函数进行计算。</p>]]></content>
<summary type="html">
<!-- #! https://zhuanlan.zhihu.com/p/181694220 -->
<p>这个博客如果只是一个复刻 QGIS 的教程的话,就没有什么价值,大家只要照着 QGIS 中的复制粘贴就可以了。所以这篇博客先来介绍一些 QGIS 中没有的。
我们使用 QG
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="Qt" scheme="http://hpdell.github.io/tags/Qt/"/>
<category term="QGIS" scheme="http://hpdell.github.io/tags/QGIS/"/>
</entry>
<entry>
<title>QGIS 二次开发笔记(2)——显示图层</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/qgisdev2-mapcanvas/"/>
<id>http://hpdell.github.io/编程/qgisdev2-mapcanvas/</id>
<published>2020-07-07T21:00:52.000Z</published>
<updated>2022-04-14T16:50:55.573Z</updated>
<content type="html"><![CDATA[<p>基于 QGIS 二次开发,最首要的功能就是显示图层。这是个看似非常简单的功能,但是在 QGIS 中写了非常复杂的代码,以支持各种数据源。但是我们在二次开发中,一般不会支持那么多的数据源。这篇博客首先以 ESRI Shapefile 数据源为例,展示加载图层的过程。</p><p>博客以创建好的工程开始,创建工程的过程网上资料很多,这里就不再赘述了。</p><h1 id="添加地图框"><a href="#添加地图框" class="headerlink" title="添加地图框"></a>添加地图框</h1><p>要想显示图层,首先要有一个显示图层的地方。在 QGIS SDK 中,使用类 <code>QgsMapCanvas</code> 显示地图。这个类需要 QT 中的 svg 组件,即</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># QgsSdkApp.pro</span></span><br><span class="line">QT += core gui xml svg</span><br></pre></td></tr></table></figure><p>如果我们想在 <code>QMainWindow</code> 派生类(我的工程中创建的类名是 <code>QgsSdkApp</code> ,以后直接用这个名称)添加一个 <code>QgsMapCanvas</code> 类型的组件,有以下三种方法:</p><ol><li>添加插件的方式:在 QT Designer 中添加插件,在 QT Designer 中绘制(我没有实现成功,理论上可以)</li><li>提升类型的方式:在 QT Designer 中使用“提升”功能,将 QWidget 组件提升为 <code>QgsMapCanvas</code> 类型</li><li>手动创建的方式:在 QgsSdkApp.cpp 中手动创建 <code>QgsMapCanvas</code> 类型的对象,添加到窗口中</li></ol><p>下面一一展示。</p><h2 id="提升类型的方式"><a href="#提升类型的方式" class="headerlink" title="提升类型的方式"></a>提升类型的方式</h2><p>选择一个组件,右键单击之后,单击“提升为”按钮,可以弹出提升窗口部件的对话框。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev2-mapcanvas/bootstrap.png"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev2-mapcanvas/bootstrap-dialog.png"><p>上面这个对话框,现在 1 所示的位置输入要提升的类型的名称,然后点击 2 位置上的按钮,在上面的列表上选中刚刚添加的提升类型,点击 3 位置上的按钮,即可将该组件提升为指定的类型(即 <code>QgsMapCanvas</code> 类型)。</p><p>然后,在 cpp 文件中已经可以访问到这个类型的组件了。</p><h2 id="手动创建的方式"><a href="#手动创建的方式" class="headerlink" title="手动创建的方式"></a>手动创建的方式</h2><p>手动创建的方式就是在窗口类的构造函数中,构造一个 <code>QgsMapCanvas</code> 类型的对象,然后添加到窗口上。这种情况下和其他在 QT 中手动创建对象没有差别,和其他一样处理即可。</p><h1 id="添加图层"><a href="#添加图层" class="headerlink" title="添加图层"></a>添加图层</h1><p>添加了地图框(在类内使用一个指针 <code>mMapCanvas</code> ,指向这个地图框)之后,下面就可以来添加图层了。首先以 ESRI Shapefile 为例,介绍一下添加 <code>QgsVectorLayer</code> 的基本方法。</p><h2 id="添加矢量图层的方法"><a href="#添加矢量图层的方法" class="headerlink" title="添加矢量图层的方法"></a>添加矢量图层的方法</h2><blockquote><p>在 QgsSdkApp.ui 中可以添加一个 action ,并拖到工具栏上创建工具按钮。点击这个按钮后开始添加图层。这个过程网上有很多教程,这里就不再赘述了。</p></blockquote><p>如果要添加 ESRI Shapefile 图层,我们需要先选择这个文件。我们可以直接弹出一个文件选择对话框。我们以选择的文件路径作为数据源的路径,文件名(不含扩展名)为图层名称。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> QgsSdkApp::on_actionShp_Layer_triggered()</span><br><span class="line">{</span><br><span class="line"> QString filePath = QFileDialog::getOpenFileName(<span class="keyword">this</span>, tr(<span class="string">"Open ESRI Shapefile"</span>), tr(<span class="string">""</span>), tr(<span class="string">"ESRI Shapefile (*.shp)"</span>));</span><br><span class="line"> <span class="function">QFileInfo <span class="title">fileInfo</span><span class="params">(filePath)</span></span>;</span><br><span class="line"> <span class="keyword">if</span> (fileInfo.exists())</span><br><span class="line"> {</span><br><span class="line"> QString fileName = fileInfo.baseName();</span><br><span class="line"> addVectorLayer(filePath, fileName, <span class="string">"ogr"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>创建 <code>addVectorLayer()</code> 函数,用于添加图层。我们可以以一种简单的方式添加图层,即直接创建 <code>QgsVectorLayer</code> 对象,并保存到一个列表(<code>mMapLayerList</code>)里。然后让地图框加载这个列表,即可显示地图。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">QgsVectorLayer *QgsSdkApp::addVectorLayer(<span class="keyword">const</span> QString &vectorLayerPath, <span class="keyword">const</span> QString &name, <span class="keyword">const</span> QString &providerKey, <span class="keyword">bool</span> guiWarning)</span><br><span class="line">{</span><br><span class="line"> QgsVectorLayer* vectorLayer = <span class="keyword">new</span> QgsVectorLayer(uri, layerName, providerKey);</span><br><span class="line"> mMapLayerList.append(vectorLayer);</span><br><span class="line"> mMapCanvas->setLayers(mMapLayerList);</span><br><span class="line"> <span class="keyword">if</span> (mMapLayerList.size() == <span class="number">1</span>)</span><br><span class="line"> {</span><br><span class="line"> QgsMapLayer* firstLayer = mMapLayerList.first();</span><br><span class="line"> QgsRectangle extent = mMapCanvas->mapSettings().layerExtentToOutputExtent(firstLayer, firstLayer->extent());</span><br><span class="line"> mMapCanvas->setExtent(extent);</span><br><span class="line"> }</span><br><span class="line"> mMapCanvas->refresh();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个函数中除了添加了地图,同时也限制了地图框地显示范围,即设置为第一个图层地显示范围。最后对地图进行了刷新。但是这种方式会遇到很多问题:</p><ol><li>如果将来要设计 Layout ,那么这个地图框的内容无法同步到 Layout 中的地图框中。</li><li>如果图层的投影到地图框的投影有多种转换方式,那么无法选择指定投影方式(尚未实现成功)</li><li>如果图层有子图层,无法选择子图层(ESRI Shapefile 中不会遇到)</li><li>如果图层需要访问验证,无法获取图层(ESRI Shapefile 中不会遇到)</li></ol><p>在 QGIS 中,使用以下代码以较为完善地设置添加地图层,同时支持了很多其他功能。函数中创建的图层直接添加到 <code>QgsProject</code> 中,以支持 Layout 等功能。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line">QgsVectorLayer *QgsSdkApp::addVectorLayer(<span class="keyword">const</span> QString &vectorLayerPath, <span class="keyword">const</span> QString &name, <span class="keyword">const</span> QString &providerKey, <span class="keyword">bool</span> guiWarning)</span><br><span class="line">{</span><br><span class="line"> QString baseName = QgsMapLayer::formatLayerName( name );</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Eliminate the need to instantiate the layer based on provider type.</span></span><br><span class="line"><span class="comment"> The caller is responsible for cobbling together the needed information to</span></span><br><span class="line"><span class="comment"> open the layer</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> QgsDebugMsg( <span class="string">"Creating new vector layer using "</span> + vectorLayerPath</span><br><span class="line"> + <span class="string">" with baseName of "</span> + baseName</span><br><span class="line"> + <span class="string">" and providerKey of "</span> + providerKey );</span><br><span class="line"></span><br><span class="line"> <span class="comment">// if the layer needs authentication, ensure the master password is set</span></span><br><span class="line"> <span class="keyword">bool</span> authok = <span class="literal">true</span>;</span><br><span class="line"> <span class="function">QRegExp <span class="title">rx</span><span class="params">( <span class="string">"authcfg=([a-z]|[A-Z]|[0-9]){7}"</span> )</span></span>;</span><br><span class="line"> <span class="keyword">if</span> ( rx.indexIn( vectorLayerPath ) != <span class="number">-1</span> )</span><br><span class="line"> {</span><br><span class="line"> authok = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">if</span> ( !QgsAuthGuiUtils::isDisabled( messageBar(), messageTimeout() ) )</span><br><span class="line"> {</span><br><span class="line"> authok = QgsApplication::authManager()->setMasterPassword( <span class="literal">true</span> );</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// create the layer</span></span><br><span class="line"> QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };</span><br><span class="line"> <span class="comment">// Default style is loaded later in this method</span></span><br><span class="line"> options.loadDefaultStyle = <span class="literal">false</span>;</span><br><span class="line"> QgsVectorLayer *layer = <span class="keyword">new</span> QgsVectorLayer( vectorLayerPath, baseName, providerKey, options );</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ( authok && layer && layer->isValid() )</span><br><span class="line"> {</span><br><span class="line"> QStringList sublayers = layer->dataProvider()->subLayers();</span><br><span class="line"> QgsDebugMsg( QStringLiteral( <span class="string">"got valid layer with %1 sublayers"</span> ).arg( sublayers.count() ) );</span><br><span class="line"></span><br><span class="line"> <span class="comment">// If the newly created layer has more than 1 layer of data available, we show the</span></span><br><span class="line"> <span class="comment">// sublayers selection dialog so the user can select the sublayers to actually load.</span></span><br><span class="line"> <span class="keyword">if</span> ( sublayers.count() > <span class="number">1</span> &&</span><br><span class="line"> ! vectorLayerPath.contains( QStringLiteral( <span class="string">"layerid="</span> ) ) &&</span><br><span class="line"> ! vectorLayerPath.contains( QStringLiteral( <span class="string">"layername="</span> ) ) )</span><br><span class="line"> {</span><br><span class="line"> QList< QgsMapLayer * > addedLayers = askUserForOGRSublayers( layer );</span><br><span class="line"></span><br><span class="line"> <span class="comment">// The first layer loaded is not useful in that case. The user can select it in</span></span><br><span class="line"> <span class="comment">// the list if he wants to load it.</span></span><br><span class="line"> <span class="keyword">delete</span> layer;</span><br><span class="line"> layer = addedLayers.isEmpty() ? <span class="literal">nullptr</span> : qobject_cast< QgsVectorLayer * >( addedLayers.at( <span class="number">0</span> ) );</span><br><span class="line"> <span class="keyword">for</span> ( QgsMapLayer *l : addedLayers )</span><br><span class="line"> askUserForDatumTransform( l->crs(), QgsProject::instance()->crs(), l );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// Register this layer with the layers registry</span></span><br><span class="line"> QList<QgsMapLayer *> myList;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//set friendly name for datasources with only one layer</span></span><br><span class="line"> QStringList sublayers = layer->dataProvider()->subLayers();</span><br><span class="line"> <span class="keyword">if</span> ( !sublayers.isEmpty() )</span><br><span class="line"> {</span><br><span class="line"> setupVectorLayer( vectorLayerPath, sublayers, layer,</span><br><span class="line"> providerKey, options );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> myList << layer;</span><br><span class="line"> QgsProject::instance()->addMapLayers( myList );</span><br><span class="line"></span><br><span class="line"> askUserForDatumTransform( layer->crs(), QgsProject::instance()->crs(), layer );</span><br><span class="line"></span><br><span class="line"> <span class="keyword">bool</span> ok;</span><br><span class="line"> layer->loadDefaultStyle( ok );</span><br><span class="line"> layer->loadDefaultMetadata( ok );</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">if</span> ( guiWarning )</span><br><span class="line"> {</span><br><span class="line"> QString message = layer->dataProvider() ? layer->dataProvider()->error().message( QgsErrorMessage::Text ) : tr( <span class="string">"Invalid provider"</span> );</span><br><span class="line"> QString msg = tr( <span class="string">"The layer %1 is not a valid layer and can not be added to the map. Reason: %2"</span> ).arg( vectorLayerPath, message );</span><br><span class="line"> visibleMessageBar()->pushMessage( tr( <span class="string">"Layer is not valid"</span> ), msg, Qgis::Critical, messageTimeout() );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">delete</span> layer;</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nullptr</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Let render() do its own cursor management</span></span><br><span class="line"> <span class="comment">// QApplication::restoreOverrideCursor();</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> layer;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>注意其中的 <code>QgsProject::instance()->addMapLayers( myList );</code> 一行,其实在 QGIS 中所有图层都是通过 <code>QgsProject</code> 进行管理的,一般来说用户无需自己管理。QGIS SDK 也提供了 <code>QgsLayerTreeView</code> 等一套类,用于显示图层的层级结构,使用也比较方便。但是如果有一些特别需要的功能,再进行自己管理。</p></blockquote><p>这个函数中还需要其他几个函数,分别定义如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br></pre></td><td class="code"><pre><span class="line">QList<QgsMapLayer *> QgsSdkApp::askUserForOGRSublayers(QgsVectorLayer *layer)</span><br><span class="line">{</span><br><span class="line"> QList<QgsMapLayer *> result;</span><br><span class="line"> <span class="keyword">if</span> ( !layer )</span><br><span class="line"> {</span><br><span class="line"> layer = qobject_cast<QgsVectorLayer *>( activeLayer() );</span><br><span class="line"> <span class="keyword">if</span> ( !layer || layer->providerType() != QLatin1String( <span class="string">"ogr"</span> ) )</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> QStringList sublayers = layer->dataProvider()->subLayers();</span><br><span class="line"></span><br><span class="line"> QgsSublayersDialog::LayerDefinitionList <span class="built_in">list</span>;</span><br><span class="line"> QMap< QString, <span class="keyword">int</span> > mapLayerNameToCount;</span><br><span class="line"> <span class="keyword">bool</span> uniqueNames = <span class="literal">true</span>;</span><br><span class="line"> <span class="keyword">int</span> lastLayerId = <span class="number">-1</span>;</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">auto</span> constSublayers = sublayers;</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">const</span> QString &sublayer : constSublayers )</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// OGR provider returns items in this format:</span></span><br><span class="line"> <span class="comment">// <layer_index>:<name>:<feature_count>:<geom_type></span></span><br><span class="line"></span><br><span class="line"> QStringList elements = splitSubLayerDef( sublayer );</span><br><span class="line"> <span class="keyword">if</span> ( elements.count() >= <span class="number">4</span> )</span><br><span class="line"> {</span><br><span class="line"> QgsSublayersDialog::LayerDefinition def;</span><br><span class="line"> def.layerId = elements[<span class="number">0</span>].toInt();</span><br><span class="line"> def.layerName = elements[<span class="number">1</span>];</span><br><span class="line"> def.count = elements[<span class="number">2</span>].toInt();</span><br><span class="line"> def.type = elements[<span class="number">3</span>];</span><br><span class="line"> <span class="comment">// ignore geometry column name at elements[4]</span></span><br><span class="line"> <span class="keyword">if</span> ( elements.count() >= <span class="number">6</span> )</span><br><span class="line"> def.description = elements[<span class="number">5</span>];</span><br><span class="line"> <span class="keyword">if</span> ( lastLayerId != def.layerId )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">int</span> count = ++mapLayerNameToCount[def.layerName];</span><br><span class="line"> <span class="keyword">if</span> ( count > <span class="number">1</span> || def.layerName.isEmpty() )</span><br><span class="line"> uniqueNames = <span class="literal">false</span>;</span><br><span class="line"> lastLayerId = def.layerId;</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">list</span> << def;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> QgsDebugMsg( "Unexpected output from OGR provider's subLayers()! " + sublayer );</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // Check <span class="keyword">if</span> the current layer uri contains the</span><br><span class="line"></span><br><span class="line"> // We initialize a selection dialog <span class="keyword">and</span> display it.</span><br><span class="line"> QgsSublayersDialog chooseSublayersDialog( QgsSublayersDialog::Ogr, QStringLiteral( "ogr" ), <span class="keyword">this</span> );</span><br><span class="line"> chooseSublayersDialog.setShowAddToGroupCheckbox( <span class="literal">true</span> );</span><br><span class="line"> chooseSublayersDialog.populateLayerTable( <span class="built_in">list</span> );</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ( !chooseSublayersDialog.exec() )</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"></span><br><span class="line"> QString name = layer->name();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">auto</span> uriParts = QgsProviderRegistry::instance()->decodeUri(</span><br><span class="line"> layer->providerType(), layer->dataProvider()->dataSourceUri() );</span><br><span class="line"> <span class="function">QString <span class="title">uri</span><span class="params">( uriParts.value( QStringLiteral( <span class="string">"path"</span> ) ).toString() )</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// The uri must contain the actual uri of the vectorLayer from which we are</span></span><br><span class="line"> <span class="comment">// going to load the sublayers.</span></span><br><span class="line"> QString fileName = QFileInfo( uri ).baseName();</span><br><span class="line"> <span class="keyword">const</span> <span class="keyword">auto</span> constSelection = chooseSublayersDialog.selection();</span><br><span class="line"> <span class="keyword">for</span> ( <span class="keyword">const</span> QgsSublayersDialog::LayerDefinition &def : constSelection )</span><br><span class="line"> {</span><br><span class="line"> QString layerGeometryType = def.type;</span><br><span class="line"> QString composedURI = uri;</span><br><span class="line"> <span class="keyword">if</span> ( uniqueNames )</span><br><span class="line"> {</span><br><span class="line"> composedURI += <span class="string">"|layername="</span> + def.layerName;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// Only use layerId if there are ambiguities with names</span></span><br><span class="line"> composedURI += <span class="string">"|layerid="</span> + QString::number( def.layerId );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ( !layerGeometryType.isEmpty() )</span><br><span class="line"> {</span><br><span class="line"> composedURI += <span class="string">"|geometrytype="</span> + layerGeometryType;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> QgsDebugMsg( <span class="string">"Creating new vector layer using "</span> + composedURI );</span><br><span class="line"> QString name = fileName + <span class="string">" "</span> + def.layerName;</span><br><span class="line"> QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };</span><br><span class="line"> options.loadDefaultStyle = <span class="literal">false</span>;</span><br><span class="line"> QgsVectorLayer *layer = <span class="keyword">new</span> QgsVectorLayer( composedURI, name, QStringLiteral( <span class="string">"ogr"</span> ), options );</span><br><span class="line"> <span class="keyword">if</span> ( layer && layer->isValid() )</span><br><span class="line"> {</span><br><span class="line"> result << layer;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> QString msg = tr( <span class="string">"%1 is not a valid or recognized data source"</span> ).arg( composedURI );</span><br><span class="line"> visibleMessageBar()->pushMessage( tr( <span class="string">"Invalid Data Source"</span> ), msg, Qgis::Critical, messageTimeout() );</span><br><span class="line"> <span class="keyword">delete</span> layer;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ( !result.isEmpty() )</span><br><span class="line"> {</span><br><span class="line"> QgsSettings settings;</span><br><span class="line"> <span class="keyword">bool</span> addToGroup = settings.value( QStringLiteral( <span class="string">"/qgis/openSublayersInGroup"</span> ), <span class="literal">true</span> ).toBool();</span><br><span class="line"> <span class="keyword">bool</span> newLayersVisible = settings.value( QStringLiteral( <span class="string">"/qgis/new_layers_visible"</span> ), <span class="literal">true</span> ).toBool();</span><br><span class="line"> QgsLayerTreeGroup *group = <span class="literal">nullptr</span>;</span><br><span class="line"> <span class="keyword">if</span> ( addToGroup )</span><br><span class="line"> group = QgsProject::instance()->layerTreeRoot()->insertGroup( <span class="number">0</span>, name );</span><br><span class="line"></span><br><span class="line"> QgsProject::instance()->addMapLayers( result, ! addToGroup );</span><br><span class="line"> <span class="keyword">for</span> ( QgsMapLayer *l : qgis::as_const( result ) )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">bool</span> ok;</span><br><span class="line"> l->loadDefaultStyle( ok );</span><br><span class="line"> l->loadDefaultMetadata( ok );</span><br><span class="line"> <span class="keyword">if</span> ( addToGroup )</span><br><span class="line"> group->addLayer( l );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Respect if user don't want the new group of layers visible.</span></span><br><span class="line"> <span class="keyword">if</span> ( addToGroup && ! newLayersVisible )</span><br><span class="line"> group->setItemVisibilityCheckedRecursive( newLayersVisible );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">bool</span> QgsSdkApp::askUserForDatumTransform(<span class="keyword">const</span> QgsCoordinateReferenceSystem &sourceCrs, <span class="keyword">const</span> QgsCoordinateReferenceSystem &destinationCrs, <span class="keyword">const</span> QgsMapLayer *layer)</span><br><span class="line">{</span><br><span class="line"> Q_ASSERT( qApp->thread() == QThread::currentThread() );</span><br><span class="line"></span><br><span class="line"> QString title;</span><br><span class="line"> <span class="keyword">if</span> ( layer )</span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// try to make a user-friendly (short!) identifier for the layer</span></span><br><span class="line"> QString layerIdentifier;</span><br><span class="line"> <span class="keyword">if</span> ( !layer->name().isEmpty() )</span><br><span class="line"> {</span><br><span class="line"> layerIdentifier = layer->name();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">const</span> QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );</span><br><span class="line"> <span class="keyword">if</span> ( parts.contains( QStringLiteral( <span class="string">"path"</span> ) ) )</span><br><span class="line"> {</span><br><span class="line"> <span class="function"><span class="keyword">const</span> QFileInfo <span class="title">fi</span><span class="params">( parts.value( QStringLiteral( <span class="string">"path"</span> ) ).toString() )</span></span>;</span><br><span class="line"> layerIdentifier = fi.fileName();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> ( layer->dataProvider() )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">const</span> QgsDataSourceUri uri( layer->source() );</span><br><span class="line"> layerIdentifier = uri.table();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> ( !layerIdentifier.isEmpty() )</span><br><span class="line"> title = tr( <span class="string">"Select Transformation for %1"</span> ).arg( layerIdentifier );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> QgsDatumTransformDialog::run( sourceCrs, destinationCrs, <span class="keyword">this</span>, mMapCanvas, title );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> QStringList <span class="title">splitSubLayerDef</span><span class="params">( <span class="keyword">const</span> QString &subLayerDef )</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">return</span> subLayerDef.split( QgsDataProvider::sublayerSeparator() );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">setupVectorLayer</span><span class="params">( <span class="keyword">const</span> QString &vectorLayerPath,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> QStringList &sublayers,</span></span></span><br><span class="line"><span class="function"><span class="params"> QgsVectorLayer *&layer,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> QString &providerKey,</span></span></span><br><span class="line"><span class="function"><span class="params"> QgsVectorLayer::LayerOptions options )</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">//set friendly name for datasources with only one layer</span></span><br><span class="line"> QgsSettings settings;</span><br><span class="line"> QStringList elements = splitSubLayerDef( sublayers.at( <span class="number">0</span> ) );</span><br><span class="line"> QString rawLayerName = elements.size() >= <span class="number">2</span> ? elements.at( <span class="number">1</span> ) : QString();</span><br><span class="line"> QString subLayerNameFormatted = rawLayerName;</span><br><span class="line"> <span class="keyword">if</span> ( settings.value( QStringLiteral( <span class="string">"qgis/formatLayerName"</span> ), <span class="literal">false</span> ).toBool() )</span><br><span class="line"> {</span><br><span class="line"> subLayerNameFormatted = QgsMapLayer::formatLayerName( subLayerNameFormatted );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ( elements.size() >= <span class="number">4</span> && layer->name().compare( rawLayerName, Qt::CaseInsensitive ) != <span class="number">0</span></span><br><span class="line"> && layer->name().compare( subLayerNameFormatted, Qt::CaseInsensitive ) != <span class="number">0</span> )</span><br><span class="line"> {</span><br><span class="line"> layer->setName( QStringLiteral( <span class="string">"%1 %2"</span> ).arg( layer->name(), rawLayerName ) );</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Systematically add a layername= option to OGR datasets in case</span></span><br><span class="line"> <span class="comment">// the current single layer dataset becomes layer a multi-layer one.</span></span><br><span class="line"> <span class="comment">// Except for a few select extensions, known to be always single layer dataset.</span></span><br><span class="line"> <span class="function">QFileInfo <span class="title">fi</span><span class="params">( vectorLayerPath )</span></span>;</span><br><span class="line"> QString ext = fi.suffix().toLower();</span><br><span class="line"> <span class="keyword">if</span> ( providerKey == QLatin1String( <span class="string">"ogr"</span> ) &&</span><br><span class="line"> ext != QLatin1String( <span class="string">"shp"</span> ) &&</span><br><span class="line"> ext != QLatin1String( <span class="string">"mif"</span> ) &&</span><br><span class="line"> ext != QLatin1String( <span class="string">"tab"</span> ) &&</span><br><span class="line"> ext != QLatin1String( <span class="string">"csv"</span> ) &&</span><br><span class="line"> ext != QLatin1String( <span class="string">"geojson"</span> ) &&</span><br><span class="line"> ! vectorLayerPath.contains( QStringLiteral( <span class="string">"layerid="</span> ) ) &&</span><br><span class="line"> ! vectorLayerPath.contains( QStringLiteral( <span class="string">"layername="</span> ) ) )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">auto</span> uriParts = QgsProviderRegistry::instance()->decodeUri(</span><br><span class="line"> layer->providerType(), layer->dataProvider()->dataSourceUri() );</span><br><span class="line"> <span class="function">QString <span class="title">composedURI</span><span class="params">( uriParts.value( QStringLiteral( <span class="string">"path"</span> ) ).toString() )</span></span>;</span><br><span class="line"> composedURI += <span class="string">"|layername="</span> + rawLayerName;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">auto</span> newLayer = qgis::make_unique<QgsVectorLayer>( composedURI, layer->name(), QStringLiteral( <span class="string">"ogr"</span> ), options );</span><br><span class="line"> <span class="keyword">if</span> ( newLayer && newLayer->isValid() )</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">delete</span> layer;</span><br><span class="line"> layer = newLayer.release();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个函数中还需要以下几个 Getter 函数,我们在窗口中提供对应的组件即可:</p><ol><li><code>QgsMessageBar* messageBar()</code> : 可以在状态栏上创建一个 <code>QgsMessageBar</code> 的对象,用这个函数获取</li><li><code>int messageTimeout()</code> : 可以直接返回 500 ,或根据配置文件、设置等返回</li><li><code>QgsMessageBar* visibleMessageBar()</code> : 可直接返回状态栏上的 <code>QgsMessageBar</code> 对象</li><li><code>QgsMapLayer* activeLayer()</code> : 需要使用 <code>QgsLayerTreeView</code> 类获取,下面详述</li></ol><p>但是现在,还不能在地图上显示,需要将 <code>QgsMapCanvas</code> 和 <code>QgsProject</code> 建立关联,才能将 <code>QgsProject</code> 中的图层同步到 <code>QgsMapCanvas</code> 中。使用的方法是在构造函数中添加如下代码:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">QgsSdkApp::QgsSdkApp(QWidget *parent)</span><br><span class="line"> : QMainWindow(parent)</span><br><span class="line"> , ui(<span class="keyword">new</span> Ui::QgsSdkApp)</span><br><span class="line">{</span><br><span class="line"> ui->setupUi(<span class="keyword">this</span>);</span><br><span class="line"> mMapCanvas = ui->centralwidget;</span><br><span class="line"> mMapCanvas->setObjectName(QStringLiteral(<span class="string">"theMapCanvas"</span>));</span><br><span class="line"></span><br><span class="line"> <span class="comment">/** [BEGIN] 添加的用于将 `QgsMapCanvas` 和 `QgsProject` 建立关联的代码 */</span></span><br><span class="line"> mLayerTreeCanvasBridge = <span class="keyword">new</span> QgsLayerTreeMapCanvasBridge(QgsProject::instance()->layerTreeRoot(), mMapCanvas, <span class="keyword">this</span>);</span><br><span class="line"> <span class="comment">/** [END] */</span></span><br><span class="line"></span><br><span class="line"> connect(ui->actionAdd_Shp_Layer, &QAction::triggered, <span class="keyword">this</span>, &QgsSdkApp::on_actionShp_Layer_triggered);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样添加了图层之后,就可以在地图上显示了。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev2-mapcanvas/load-layer.png"><h2 id="添加其他图层"><a href="#添加其他图层" class="headerlink" title="添加其他图层"></a>添加其他图层</h2><p>其他图层的添加方法,都可以从 QGIS 的代码中进行参考。在 qgisapp.cpp 文件中,有这个函数</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> QgisApp::dataSourceManager( <span class="keyword">const</span> QString &pageName )</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> ( ! mDataSourceManagerDialog )</span><br><span class="line"> {</span><br><span class="line"> mDataSourceManagerDialog = <span class="keyword">new</span> QgsDataSourceManagerDialog( mBrowserModel, <span class="keyword">this</span>, mapCanvas() );</span><br><span class="line"> connect( <span class="keyword">this</span>, &QgisApp::connectionsChanged, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::refresh );</span><br><span class="line"> connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::connectionsChanged, <span class="keyword">this</span>, &QgisApp::connectionsChanged );</span><br><span class="line"> connect( mDataSourceManagerDialog, SIGNAL( addRasterLayer( QString <span class="keyword">const</span> &, QString <span class="keyword">const</span> &, QString <span class="keyword">const</span> & ) ),</span><br><span class="line"> <span class="keyword">this</span>, SLOT( addRasterLayer( QString <span class="keyword">const</span> &, QString <span class="keyword">const</span> &, QString <span class="keyword">const</span> & ) ) );</span><br><span class="line"> connect( mDataSourceManagerDialog, SIGNAL( addVectorLayer( QString <span class="keyword">const</span> &, QString <span class="keyword">const</span> &, QString <span class="keyword">const</span> & ) ),</span><br><span class="line"> <span class="keyword">this</span>, SLOT( addVectorLayer( QString <span class="keyword">const</span> &, QString <span class="keyword">const</span> &, QString <span class="keyword">const</span> & ) ) );</span><br><span class="line"> connect( mDataSourceManagerDialog, SIGNAL( addVectorLayers( QStringList <span class="keyword">const</span> &, QString <span class="keyword">const</span> &, QString <span class="keyword">const</span> & ) ),</span><br><span class="line"> <span class="keyword">this</span>, SLOT( addVectorLayers( QStringList <span class="keyword">const</span> &, QString <span class="keyword">const</span> &, QString <span class="keyword">const</span> & ) ) );</span><br><span class="line"> connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addMeshLayer, <span class="keyword">this</span>, &QgisApp::addMeshLayer );</span><br><span class="line"> connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::showStatusMessage, <span class="keyword">this</span>, &QgisApp::showStatusMessage );</span><br><span class="line"> connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addDatabaseLayers, <span class="keyword">this</span>, &QgisApp::addDatabaseLayers );</span><br><span class="line"> connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::replaceSelectedVectorLayer, <span class="keyword">this</span>, &QgisApp::replaceSelectedVectorLayer );</span><br><span class="line"> connect( mDataSourceManagerDialog, <span class="keyword">static_cast</span><<span class="keyword">void</span> ( QgsDataSourceManagerDialog::* )()>( &QgsDataSourceManagerDialog::addRasterLayer ), <span class="keyword">this</span>, <span class="keyword">static_cast</span><<span class="keyword">void</span> ( QgisApp::* )()>( &QgisApp::addRasterLayer ) );</span><br><span class="line"> connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::handleDropUriList, <span class="keyword">this</span>, &QgisApp::handleDropUriList );</span><br><span class="line"> connect( <span class="keyword">this</span>, &QgisApp::newProject, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::updateProjectHome );</span><br><span class="line"> connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::openFile, <span class="keyword">this</span>, &QgisApp::openFile );</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> mDataSourceManagerDialog->reset();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Try to open the dialog on a particular page</span></span><br><span class="line"> <span class="keyword">if</span> ( ! pageName.isEmpty() )</span><br><span class="line"> {</span><br><span class="line"> mDataSourceManagerDialog->openPage( pageName );</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> ( QgsSettings().value( QStringLiteral( <span class="string">"/qgis/dataSourceManagerNonModal"</span> ), <span class="literal">true</span> ).toBool() )</span><br><span class="line"> {</span><br><span class="line"> mDataSourceManagerDialog->show();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> mDataSourceManagerDialog->exec();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个函数中有 <code>addRasterLayer</code> <code>addVectorLayer</code> <code>addMeshLayer</code> 等函数,是添加不同类型的图层的方法。可以直接查看这些方法,学习其中的方法,放到工程中来。</p><blockquote><p>在下篇博客中,我计划介绍添加 CSV 类型数据的方法。</p></blockquote><h1 id="显示图层树"><a href="#显示图层树" class="headerlink" title="显示图层树"></a>显示图层树</h1><p>一般情况下我们都需要使用图层树来对图层进行管理。下面我们就在界面上添加 <code>QgsLayerTreeView</code> 对象。</p><p>我们首先在界面上创建一个 QWidget 组件,提升为 <code>QgsLayerTreeView</code> 类型。然后再构造函数中给其设置 Model 等。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">QgsSdkApp::QgsSdkApp(QWidget *parent) : QMainWindow(parent), ui(<span class="keyword">new</span> Ui::QgsSdkApp)</span><br><span class="line">{</span><br><span class="line"> ui->setupUi(<span class="keyword">this</span>);</span><br><span class="line"> mMapCanvas = ui->centralwidget;</span><br><span class="line"> mMapCanvas->setObjectName(QStringLiteral(<span class="string">"theMapCanvas"</span>));</span><br><span class="line"> mLayerTreeCanvasBridge = <span class="keyword">new</span> QgsLayerTreeMapCanvasBridge(QgsProject::instance()->layerTreeRoot(), mMapCanvas, <span class="keyword">this</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/** [BEGIN] 设置 QgsLayerTreeView 的 Model */</span></span><br><span class="line"> QgsLayerTreeModel* model = <span class="keyword">new</span> QgsLayerTreeModel(QgsProject::instance()->layerTreeRoot(), <span class="keyword">this</span>);</span><br><span class="line"> ui->layerTreeView->setModel(model);</span><br><span class="line"> ui->layerTreeView->setObjectName(QStringLiteral( <span class="string">"theLayerTreeView"</span> ));</span><br><span class="line"> <span class="comment">/** [END] */</span></span><br><span class="line"></span><br><span class="line"> mInfoBar = <span class="keyword">new</span> QgsMessageBar(<span class="keyword">this</span>);</span><br><span class="line"> ui->statusbar->addWidget(mInfoBar);</span><br><span class="line"></span><br><span class="line"> connect(ui->actionAdd_Shp_Layer, &QAction::triggered, <span class="keyword">this</span>, &QgsSdkApp::on_actionShp_Layer_triggered);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>QT 中采用的 MVC 模型。 QT 中提供了 QTreeView 、 QListView 、 QTableView 等视图(View),也提供了 QAbstractItemModel 、 QAbstractListModel 、 QAbstractTableModel 三种模型(Model),也会提供了一些 Delegate 委托(相当于控制器 Controller)。</p></blockquote><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev2-mapcanvas/layer-tree-view.png"><p>但是我们这个图层树仅仅有一个最基本的功能,而在 QGIS 中排序、右键菜单丰富的功能。对于右键菜单,QGIS 中使用了 Provider 的方式提供右键菜单的菜单项,我们需要将这些 Provider 的代码复制过来,添加到工程中。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// qgisapp.cpp [4493行]</span></span><br><span class="line">mLayerTreeView->setMenuProvider( <span class="keyword">new</span> QgsAppLayerTreeViewMenuProvider( mLayerTreeView, mMapCanvas ) );</span><br></pre></td></tr></table></figure><p>对于排序等其他功能,我们可以按需添加。</p>]]></content>
<summary type="html">
<p>基于 QGIS 二次开发,最首要的功能就是显示图层。这是个看似非常简单的功能,但是在 QGIS 中写了非常复杂的代码,以支持各种数据源。
但是我们在二次开发中,一般不会支持那么多的数据源。这篇博客首先以 ESRI Shapefile 数据源为例,展示加载图层的过程。</p>
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="Qt" scheme="http://hpdell.github.io/tags/Qt/"/>
<category term="QGIS" scheme="http://hpdell.github.io/tags/QGIS/"/>
</entry>
<entry>
<title>QGIS 二次开发笔记(1)——环境配置</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/qgisdev1-build/"/>
<id>http://hpdell.github.io/编程/qgisdev1-build/</id>
<published>2020-03-28T10:34:50.000Z</published>
<updated>2022-04-14T16:50:55.561Z</updated>
<content type="html"><![CDATA[<p>众所周知,QGIS是一个用户界面友好的桌面地理信息系统,可运行在Linux、Unix、Mac OSX和Windows等平台之上。QGIS 基于 Qt 开发,除了提供可执行程序,还提供了一套用于二次开发的接口,可进行跨平台地理信息系统软件的定制开发。</p><p>正如所有的开发都是从安装编译器开始的,所有的二次开发都是从配置环境开始的。然而,QGIS 的开发环境实属难配,如果要配置可调试的 Debug 环境更是难上加难。经过了一段时间的开发之后,笔者总结了 QGIS 开发环境配置的基本过程,以及容易遇到的坑,在这里和大家分享。</p><a id="more"></a><h1 id="Release-版开发环境配置"><a href="#Release-版开发环境配置" class="headerlink" title="Release 版开发环境配置"></a>Release 版开发环境配置</h1><p>Release 版开发环境配置相对比较简单,可以使用 OSGeo4W 直接下载各种预编译好的库。除了下载比较慢以外,基本不会遇到其他问题。</p><h2 id="使用-OSGeo4W-安装相关库"><a href="#使用-OSGeo4W-安装相关库" class="headerlink" title="使用 OSGeo4W 安装相关库"></a>使用 OSGeo4W 安装相关库</h2><p>在 OSGeo4W 官网下载安装程序后,即可进行安装。</p><ol><li>安装方式选择 Advanced Install</li><li>安装源选择 Install from Internet ,当然如果是帮别人装可以选择 Download Without Installing ,如果用别人下载好的可以 Install from Local Directory</li><li>安装目录可以自己选</li><li>本地包下载路径可以自己选,安装完后就会删除</li><li>网络连接方式,如果没有梯子,就可以选择 Direct Connection ;有的话可以选择 Use IE5 Settings 直接导入系统代理配置,或者选择 Use HTTP/FTP Proxy 自己定义代理</li><li>然后会下载一些包,进入到下载点选择,可以直接选择 <a href="http://download.osgeo.org" target="_blank" rel="noopener">http://download.osgeo.org</a> 那个。如果觉得网速比较慢,可以使用 GWmodel 实验室提供的 OSGeo4W 的镜像,在 User URL 里面输入 <a href="http://gwmodel.whu.edu.cn/mirrors/osgeo4w" target="_blank" rel="noopener">http://gwmodel.whu.edu.cn/mirrors/osgeo4w</a> 然后点击 Add 按钮即可添加。</li><li>安装内容,可以在搜索框里面输入 qgis 进行搜索。可以看到有多种 qgis 版本安装包。综合各种因素考虑,最好是选择 qgis-rel-dev 这个版本的包,这个包是最新的 QGIS Release 版本的源代码(这里我之前安装了 qgis-dev)。点击 Skip 按钮选择版本号。OSGeo4W 会自动下载各种依赖包。</li><li>点击下一步后,要同意一些用户协议,之后即可进行安装。</li></ol><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/advanced-install.png"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/download-source.png"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/root-install-directory.png"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/install-cache-directory.png"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/internet-connection.png"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/download-site.png"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/qgis-rel-dev.png"><h2 id="Qt-安装"><a href="#Qt-安装" class="headerlink" title="Qt 安装"></a>Qt 安装</h2><p>为什么先说 OSGeo4W 安装再说 Qt 安装呢?因为 Qt 要安装的版本与 OSGeo4W 中安装的版本有关。我们首先要查看已经安装了的 Qt 版本号,然后再安装对应的 Qt 开发工具。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/qt-version.png" title="QT库的版本号"><p>当然,如果你不准备进行 Debug 环境的配置,那么其实可以只安装一个 Qt Creator 等相关工具。开发的时候直接使用 OSGeo4W 安装的 Qt 库进行开发。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/qt-install.png"><h2 id="工程中引用"><a href="#工程中引用" class="headerlink" title="工程中引用"></a>工程中引用</h2><p>工程引用其实非常简单,只需要在 pro 文件中加入 include 和 lib 的生命引用即可,此外再加一个 GDAL 的配置。</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## QGIS</span></span><br><span class="line">INCLUDEPATH += <span class="string">"<span class="variable">$(OSGEO_HOME)</span>/include"</span></span><br><span class="line">INCLUDEPATH += <span class="string">"<span class="variable">$(OSGEO_HOME)</span>/apps/qgis-rel-dev/include"</span></span><br><span class="line">LIBS += -L<span class="string">"<span class="variable">$(OSGEO_HOME)</span>/apps/qgis-rel-dev/lib"</span> -lqgis_core -lqgis_gui</span><br><span class="line">LIBS += -L<span class="string">"<span class="variable">$(OSGEO_HOME)</span>/lib"</span> -lgdal_i</span><br><span class="line">GDAL_DATA = <span class="string">".\share\gdal"</span></span><br><span class="line"><span class="comment">## QGIS END</span></span><br></pre></td></tr></table></figure><p>这里面引用了一个 <code>OSGEO_HOME</code> 的环境变量,可以将这个变量设置到系统环境变量中,或者加入到工程环境变量中,变量值就是 OSGeo4W 的安装目录。</p><h2 id="运行配置"><a href="#运行配置" class="headerlink" title="运行配置"></a>运行配置</h2><blockquote><p>该部分主要参考知乎文章 <a href="https://zhuanlan.zhihu.com/p/83805300" target="_blank" rel="noopener">QtCreator进行QGis二次开发(1)</a> ,有修改。</p></blockquote><p>我们编译好自己的工程,运行的时候会提示缺 DLL 。常用的方法就是把需要的 DLL 放到工程生成的可执行程序目录下。主要需要这些 DLL :(为了描述方便,我直接写我的 Qt 工具的路径 C:/Qt/5.11.2/msvc2017_64 )</p><ol><li>Qt 相关的 DLL 。可以使用 Qt 提供的 windeploy 工具直接部署。但是由于 QGIS 所引用的 DLL 用这个工具是无法部署过来的,因此还需要将缺少的 C:/Qt/5.11.2/msvc2017_64/bin 中的 DLL 拷贝过来(注意不需要以 d.dll 结尾的动态链接库)。如果嫌 windeploy 麻烦,可以直接将 C:/Qt/5.11.2/msvc2017_64/bin 的 DLL 拷贝过来,然后再把 C:/Qt/5.11.2/msvc2017_64/plugins 文件夹拷贝到工程生成的 exe 同级目录下(示例图中采用的是这种方法)。</li><li>OSGeo4W 安装的 DLL 。直接把 $(OSGEO_HOME)/bin 下的 DLL 拷过来。</li><li>QGIS 相关的 DLL 。直接把 $(OSGEO_HOME)/apps/qgis-rel-dev/bin 下的 DLL 拷过来。</li><li>QGIS 相关的 plugins 。直接把 $(OSGEO_HOME)/apps/qgis-rel-dev/plugins 下的 DLL 拷过来。</li></ol><p>此外还有一些资源文件,主要有</p><ol><li>$(OSGEO_HOME)/share/gdal 文件夹拷贝到工程生成的 exe 文件同级 share 目录下(如果没有就新建一个)</li><li>$(OSGEO_HOME)/apps/qgis-rel-dev/resources 文件夹拷贝到工程生成的 exe 文件目录下。</li></ol><p>此外还有一个运行时的环境变量 <code>PROJ_LIB</code> 需要配置,配置到 $(OSGEO_HOME)/apps/proj-dev/share/proj 目录即可。如果没有这个目录,就配置到 $(OSGEO_HOME)/share/proj 。这个主要是版本的问题。</p><blockquote><p>如果后面开发中遇到了无法进行投影转换的问题,而且有 proj.db 相关数据库查询的保存,则很有可能就是这个 <code>PROJ_LIB</code> 变量的位置配错了,配成了不同版本的。因为 Proj 7.0 版本的数据库和 6.x 版本的数据库不一样。</p></blockquote><p>这样我们的程序就可以运行了。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/project-release-dir.png"><h1 id="Debug-版环境的配置"><a href="#Debug-版环境的配置" class="headerlink" title="Debug 版环境的配置"></a>Debug 版环境的配置</h1><blockquote><p>事先声明:笔者编译出了 DEBUG 版可以执行的 qgis.exe ,但是不知道什么原因无法加载了 qgis_app.dll 因而无法运行。但是编译出来的库是可以在二次开发中使用的。</p></blockquote><p>因为 OSGeo4W 只提供了 Release 版的库,如果要 Debug 版需要自己编译。整个过程没有那么复杂,但是容易遇到一些奇奇怪怪的问题。</p><p>首先需要下载 QGIS 最新 Release 版本的代码。你会发现最新 Release 的代码和 qgis-rel-dev 的版本是一致的,这就是之前选择 qgis-rel-dev 的原因。</p><p>然后用 Qt Creator 打开这个库的 CMakeList.txt 文件,就开始进行 CMake 工程的配置。</p><h2 id="配置之前的准备"><a href="#配置之前的准备" class="headerlink" title="配置之前的准备"></a>配置之前的准备</h2><p>配置之前要提前编译如下库的 Debug 版本的 lib 文件和 dll 文件:</p><ol><li>qca (非常重要,必须编译)</li><li>qwt (最好编译)</li><li>qtkeychain (最好编译,但理论上可以不编译)</li><li>expat (可以不编译)</li></ol><p>这些库基本都有 CMakeList.txt 文件,因此可以直接使用 Qt Creator 打开编译。但是如果用 CMake GUI 配置好生成 VS 解决方案,用 VS 打开进行编译,你会发现有一个 BUILD_ALL 和 INSTALL 项目,编译好后直接生成这个项目,就可以安装到 CMake 配置时 <code>INSTALL_PREFIX</code> 变量指定的目录。</p><blockquote><p>理论上,Debug 程序是可以调用 Release 版本的库的,但是在 GUI 环境下,混用 Debug 和 Release 的库会导致程序崩溃。对于 QCA ,如果使用了 Release 版本的 QCA 库,那么程序运行的时候在初始化 QCA 的时候就会发生崩溃。所以 QCA 必须编译 DEBUG 版本。qwt 和 qtkeychain 最好也编译一下,毕竟也不麻烦。但是 qtkeychain 好像并不涉及 GUI ,如果嫌麻烦可以不编译。expat 库纯粹是因为其 release 和 debug 版本的库文件名不一样,可能 DEBUG 和 RELEASE 有区别,因此我编译了一下,但是直接用 OSGeo4W 的应该也是可以的。</p></blockquote><p>至于如何编译这些库,网上有其他教程,这是个体力活,这里就不赘述了。</p><p>还要安装 Cygwin ,并且安装如下两个工具。安装完记得把 Cygwin/bin 目录添加到 <code>PATH</code> 环境变量中。</p><ul><li>bison</li><li>flex</li></ul><p>还需要安装 ninja.exe 并拷贝到 $(OSGEO_HOME)/bin (其实理论上放到系统 <code>PATH</code> 环境变量包含的目录中都可以)。</p><h2 id="编译配置"><a href="#编译配置" class="headerlink" title="编译配置"></a>编译配置</h2><p>打开 QGIS 的 CMakeLists.txt 文件,会有许多东西需要配置,因为所有的依赖库都要指定其 INCLUDE_DIR 和 LIBRARY 。用 OSGeo4W 安装的库都可以这样指定</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/osgeo4w-include-lib.png" title="OSGeo4W安装库的配置方式"><p>自己编译的库也用类似的方式指定就可以了。</p><p>选择 <code>WITH_QTWEBKIT</code> 最好去掉,因为在 Qt 5.5 以后的版本中已经去掉了 QtWebKit 组件。虽然下个版本会加回来。选择 <code>WITH_BINDINGS</code>(Python 相关模块)、<code>WITH_SERVER</code>、<code>WITH_POSTGRE</code> 如果不需要相关的功能就可以去掉,如果勾选的话就需要相关的库。</p><p>选项 <code>WITH_INTERNEL_MDAL</code> 可以勾上,让 QGIS 自己编译 MDAL,当然就需要指定 HDF5、NetCDF 等库的位置,如果没有这些库可以从 OSGeo4W 中安装。如果关闭,那就自己编译 MDAL,然后提供 <code>MDAL_INCLUDE_DIR</code> 和 <code>MDAL_LIBRARY</code> 。</p><p><strong>还要注意,如果勾选了 <code>WITH_INTERNEL_MDAL</code> 要给 <code>CMAKE_CXX_FLAGS</code> 开头的几个变量后面要添加 <code>/D H5_BUILT_AS_DYNAMIC_LIB"</code> 选项</strong>,否则无法编译。另外,<strong>需要在这几个变量中加上 <code>/utf-8</code> 选项</strong>,否则会报告“常量中有换行符”等错误。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/CMAKE_CXX_FLAGS.png" title="CMAKE编译选项"><p>然后代码基本就可以编译了。如果 CMAKE 还是出错,就采用以下三种方式解决:</p><ol><li>从 OSGeo4W 中下载相应的库文件</li><li>自己编译以提供相应的库文件</li><li><strong>推荐做法:关闭相关的 WITH 选项</strong></li></ol><h2 id="编译后"><a href="#编译后" class="headerlink" title="编译后"></a>编译后</h2><p>如果使用 VS 编译的,那么有 INSTALL 工程可以直接安装。如果是 QT 编译的,那么就手动把 dll 和 lib 等库拷贝出来吧, include 文件可以使用 qgis-rel-dev 的。还有 resource 文件夹别忘了。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/qgisdev1-build/qgis-debug.png"><p>至此,所有 QGIS 开发的环境配置已经完成了。</p>]]></content>
<summary type="html">
<p>众所周知,QGIS是一个用户界面友好的桌面地理信息系统,可运行在Linux、Unix、Mac OSX和Windows等平台之上。
QGIS 基于 Qt 开发,除了提供可执行程序,还提供了一套用于二次开发的接口,可进行跨平台地理信息系统软件的定制开发。</p>
<p>正如所有的开发都是从安装编译器开始的,所有的二次开发都是从配置环境开始的。
然而,QGIS 的开发环境实属难配,如果要配置可调试的 Debug 环境更是难上加难。
经过了一段时间的开发之后,笔者总结了 QGIS 开发环境配置的基本过程,以及容易遇到的坑,在这里和大家分享。</p>
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="Qt" scheme="http://hpdell.github.io/tags/Qt/"/>
<category term="QGIS" scheme="http://hpdell.github.io/tags/QGIS/"/>
</entry>
<entry>
<title>测试使用 StackEdit 更新博客</title>
<link href="http://hpdell.github.io/%E9%9A%8F%E7%AC%94/test-stackedit/"/>
<id>http://hpdell.github.io/随笔/test-stackedit/</id>
<published>2020-01-09T22:21:30.000Z</published>
<updated>2022-04-14T16:50:55.573Z</updated>
<content type="html"><![CDATA[<p>这篇博客内容是通过 StackEdit 编写的,只是尝试一下使用这个工具编写然后同步,触发 Travis 持续集成,然后更新博客的流程。</p><blockquote><p>Written with <a href="https://stackedit.io/" target="_blank" rel="noopener">StackEdit</a>.<!--stackedit_data:eyJoaXN0b3J5IjpbMjA5ODczMjYxNV19--></p></blockquote>]]></content>
<summary type="html">
<p>这篇博客内容是通过 StackEdit 编写的,只是尝试一下使用这个工具编写然后同步,触发 Travis 持续集成,然后更新博客的流程。</p>
<blockquote>
<p>Written with <a href="https://stackedit.io/" tar
</summary>
<category term="随笔" scheme="http://hpdell.github.io/categories/%E9%9A%8F%E7%AC%94/"/>
</entry>
<entry>
<title>探索性因子分析</title>
<link href="http://hpdell.github.io/%E7%90%86%E8%AE%BA/exploratory-factor-analysis/"/>
<id>http://hpdell.github.io/理论/exploratory-factor-analysis/</id>
<published>2019-09-03T19:58:46.000Z</published>
<updated>2022-04-14T16:50:55.433Z</updated>
<content type="html"><![CDATA[<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>在之前的章节中,我们讨论了主成分分析——一种多元数据降维以及理解变量间关联模式的方法。在这个章节中,我们讨论,这是一种类似的方法,基于一种不同的隐含的模型,被称作””。虽然这两种方法比较类似,但探索性因子分析经常被用于完成和主成分分析相同的目标。我们这里的目的是强调他们之间概念上的区别。公因子模型关于数据集中的每个变量是如何计量的有明确的假设。模型认为每次计量的变化是由几个相对较少的共同因子造成的(即两个或多个变量之间不可观测的共同特性)。尽管一个变量实际上可能有一个或多个特性因子,统计上不可能将这些特殊变量混淆。探索性因子分析的目标是发现共同因子(区别于特性因子)并解释它们和观测数据之间的关系。因此,尽管用于完成探索性因子分析的解决方案和主成分分析类似,但他们背后的模型是不相同的。</p><p>在探索性因子分析中,我们让观测到的数据间关联模式决定因子构成。在《验证性因子分析》中,我们转向验证性数据分析,利用一些关于因子构成的先验知识,去检验他们是否与数据一致。在验证性数据分析中,背后的模型与探索性因子分析是相同的,但是求解过程则大不相同。</p><p>公因子模型提供了一个明确的框架让我们可以对数据计量属性进行评估。在这个章节提供的案例中,我们假设特性因子的变化可以单纯地由计量误差解释。广义地,为了区分特性方差(即特性因子的方差)和误差方差(即每个变量计量误差的方差),计量可靠性的独立评估是很必要的。如果没有这种独立评估,我们就假设特性因子方差反映了计量误差。这让我们对计量的可靠性有了一定的了解:误差方差越小,计量越可靠。《验证性因子分析》中有关于可靠性的形式化概念。</p><p>在这章节的编写过程中,我们特别注重使用旋转来提高因子分析解的解释性。因为探索性因子分析的方向是随意的,有时候选择有简单结构的解才是有意义的(即在某种意义上最易于解释的一种,由 Thurstone 在1947年提出)。实际上,在对数据进行主成分分析时旋转也同样可行。这里所介绍的所有旋转方法都可以应用于主成分分析。</p><h2 id="潜在应用"><a href="#潜在应用" class="headerlink" title="潜在应用"></a>潜在应用</h2><p>探索性因子分析与大多数主成分分析的应用场景相同;而公因子模型当测量模型的明确假设是适当时更合适。下面介绍探索性因子分析的两种说明性应用:分析”潜在特征”或”不可观测的特性”、使用因子得分分析依赖性。</p><h3 id="分析”潜在特征”或”不可观测的特性”"><a href="#分析”潜在特征”或”不可观测的特性”" class="headerlink" title="分析”潜在特征”或”不可观测的特性”"></a>分析”潜在特征”或”不可观测的特性”</h3><p>有的时候区分数据变量和它被设计用于计量的概念是非常重要的。在处理物理特性时,例如长度和重量(测量仪器具有高精度),这种区分是不必要的,因为目标属性几乎可以被完美计量。但是,当处理态度、信仰、感知以及其他心理学特性时,我们的计量方法是不完美的。</p><p>在市场,调查研究员可能对获取某个概念(例如”顾客满意度”)的信息感兴趣,以更好理解这个概念以及企业行为对它的影响。设计一个单一的调查问题来准确认知如”顾客满意度”这种概念,即使是可能的,也非常困难。相反的,研究者可能回设计一个包含几个问题的调查问卷,每个问题并不能完美反映”顾客满意度”,但能够反应某一个方面。使用因子分析,可以找到这些问题背后的共同变量(很可能反映了客户潜在的满意度)并分离出计量中的非系统误差。从公因子模型中得到的因子得分可以作为后续分析和建模中顾客满意度的一个指数(或多个指数,取决于潜在变量的个数)。</p><p>Aaker(1997)使用探索性因子分析研究了品牌特性的几个维度。 为了处理 114个特性指标(从心理学和市场调研研究中产生的 309个候选指标中筛选得到的), Aaker 让受访者对一组10个品牌中的每一个进行114 个特性指标的 5 级尺度打分(从 1 到 5 ,1 代表完全没有体现,5代表非常能够体现)。她使用了四个不同的品牌数据集,每组都有一个焦点品牌(李维斯牛仔)和九个其他的(都是非常突出的、广为人知的国民品牌,涵盖了多个不同的产品类型),一共37 个不同的品牌。 Aaker对打分结果按人数平均(每个品牌在每个指标上都被大约 150 到 160个受访者打分,除了李维斯被所有人打分) 并对指标间 $114 \times 114$维的相关矩阵进行了因子分析。</p><p>Aaker 选择了一个五个因子的解,解释了 90% 的指标差异。在进行了旋转之后,她将这些因子标记为: 诚意(sincerity,解释了 26.5%的差异)、刺激(excitement,25.1%)、竞争力(competence,17.5%)、世故(sophistication,11.9%)和粗犷(ruggedness,8.8%)。表展示了这五个因子的一些最高度相关的指标。基于这个研究的结果, Aaker 继续构造并验证了 42项测度用于测量者五个品牌特性的成分。</p><table><thead><tr><th>Sincerity</th><th>Excitement</th><th>Competence</th><th>Sophistication</th><th>Ruggedness</th></tr></thead><tbody><tr><td>Honest</td><td>Daring</td><td>Reliable</td><td>Glamorous</td><td>Tough</td></tr><tr><td>Genuine</td><td>Spirited</td><td>Responsible</td><td>Pretentious</td><td>Strong</td></tr><tr><td>Cheerful</td><td>Imaginative</td><td>Dependable</td><td>Charming</td><td>Outdoorsy</td></tr><tr><td>Down-to-earth</td><td>Up-to-date</td><td>Efficient</td><td>Romantic</td><td>Masculine</td></tr><tr><td>Friendly</td><td>Cool</td><td>Intelligent</td><td>Upper class</td><td></td></tr><tr><td></td><td></td><td>Successful</td><td>Smooth</td></tr></tbody></table><h3 id="使用因子得分分析依赖性"><a href="#使用因子得分分析依赖性" class="headerlink" title="使用因子得分分析依赖性"></a>使用因子得分分析依赖性</h3><p>和主成分分析一样,减少维数往往是因子分析的主要目标。一方面,这么做有助于对数据进行可视化;另一方面,也有助于增加模型简洁性。例如,在一个关于新的豪华车概念的大型市场调研中,Roberts(1984)调查了162 个目标消费者关于他们在汽车的认知。他使用九个维度来计量他们在熟悉的汽车模型的认值:奢华、样式、可靠性、油耗、安全、维修成本、质量、续航以及道路偏好。他的最终目标是建立一个模型,将认知和偏好联系起来;但是可用于拟合这个模型的自由维度数太有限。因此他使用因子分析来看看他是否可以找到一组更少数目的潜在公因子可以替代。</p><p>Roberts 发现一个双因子解解释了这九个特性中 60% 的差异。Roberts然后旋转了解(使用方差最大法)来使得它更容易解释。指标和因子之间的相关系数被称为,如下表所示;相关系数最高的是用下划线标出。这种模式指示第一个因子(与奢华、样式、安全性、道路偏好等指标高度相关)反映了车的情感诉求,而第二个指标(与可靠性、油耗、维修成本、质量和续航相关)反映了车的经济性。Roberts 将这两个因子标记为吸引力和合理性。</p><table><thead><tr><th></th><th>Appealing</th><th>Sensible</th></tr></thead><tbody><tr><td>Luxury</td><td><u>0.884</u></td><td>-0.051</td></tr><tr><td>Style</td><td><u>0.748</u></td><td>0.153</td></tr><tr><td>Reliability</td><td>0.396</td><td><u>0.691</u></td></tr><tr><td>Fuel Economy</td><td>-0.202</td><td><u>0.786</u></td></tr><tr><td>Safety</td><td><u>0.720</u></td><td>0.172</td></tr><tr><td>Maintenance</td><td>0.149</td><td><u>0.756</u></td></tr><tr><td>Quality</td><td>0.501</td><td><u>0.650</u></td></tr><tr><td>Durable</td><td>0.386</td><td><u>0.677</u></td></tr><tr><td>Performance</td><td><u>0.686</u></td><td>0.391</td></tr></tbody></table><p>使用这个模型, Roberts计算每个被打分的车模在每个因子得分的平均值,然后使用这些因子得分来回归前面所述的受访者喜好。使用这两个因子,他能够解释 30% 的受访者喜好的差异(依据调整);这两个因子都高度显著。 相反,当 Roberts 使用 9个指标来回归受访者喜好时,拟合数据的能力仅有轻微的提高(调整 是 33%),而他参数估计值的标准差大幅增加,由于指标间的多重共线性(实际上,九个系数中只有两个在0.05 等级下显著)。 使用这个简洁的因子模型, Roberts能够评估新概念车的相对定位,并精确预测市场价值。</p><h1 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h1><h2 id="直观认识"><a href="#直观认识" class="headerlink" title="直观认识"></a>直观认识</h2><p>和主成分分析一样,对因子分析的直观感觉也最好是通过一个简单的例子进行。考虑 Holzinger 和 Swineford 进行的针对儿童的心理学考试(1939)。他们对七、八年级的儿童进行了一些不同的考试。为了使问题简化,我们主要关注下面五个考试:篇章理解($PARA$)、句子完成($SENT$)、单词含义($WORD$)、加法($ADD$)、数点($DOTS$)。我们使用变量 $X_1$到 $X_5$表示这五个不同的考试。他们间的相关系数(基于 145个儿童组成的样本)如下表所示。</p><table><thead><tr><th></th><th>$PARA$</th><th>$SENT$</th><th>$WORD$</th><th>$ADD$</th><th>$DOTS$</th></tr></thead><tbody><tr><td>$PARA$</td><td>$1.000$</td><td></td><td></td><td></td><td></td></tr><tr><td>$SENT$</td><td>$0.722$</td><td>$1.000$</td><td></td><td></td><td></td></tr><tr><td>$WORD$</td><td>$0.714$</td><td>$0.685$</td><td>$1.000$</td><td></td><td></td></tr><tr><td>$ADD $</td><td>$0.203$</td><td>$0.246$</td><td>$0.170$</td><td>$1.000$</td><td></td></tr><tr><td>$DOTS$</td><td>$0.095$</td><td>$0.181$</td><td>$0.113$</td><td>$0.585$</td><td>$1.000$</td></tr></tbody></table><p>在因子分析中,我们假设考试得分可以用一个潜在公因子和几个特殊因子(每个考试一个特殊因子)的函数来描述。也就是说,例如,我们相信这些学生的考试得分背后都有一个公因子,记为 $\xi$。 这个因子可能反映了每个学生的智力或应试能力。我们的但因子模型表示,对考试 $i$的得分 $X_i$是因子 $\xi$(对所有五个考试来说都是相同的) 和考试 $i$的特有的因子——不妨记为$\delta_i$——的函数。在很多可以用于估计公因子模型参数的方法中,所有我们实际可以估计的是唯一方差(或称”唯一性”),即这个特殊因子和测量误差方差的和。我们假设这个特殊因子方差完全由测量误差引起,反映考试在完美捕获背后共同因子能力的不足。由于特殊因子仅影响他们特别针对的计量(即 $\delta_i$仅影响考试 $i$),我们的后续处理将在假设这些特殊因子 $\delta$互不相关 (即对于所有不同[^1] 的 $i$和 $j$,$\delta_i$和 $\delta_j$之间的相关系数是 0)。且与潜在的公因子 $\xi$也不相关的假设下进行。这些都是公因子模型的标准假设。</p><p>这个单因子模型可以用图中的示意图所表示。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/理论/exploratory-factor-analysis/path-diagram-of-one-factor-model-with-five-variables.png"><p>图中指向每个考试变量的箭头(即观测计量值,用盒形表示)指示测量中变异的贡献源。这种情况下,每个测量值 $X_i$有两个变异的贡献源(都不可观测):公因子$\xi$和一个特殊因子 $\delta_i$。 用公式的形式,可以写作下式$$\begin{aligned} X_1 & = \lambda_1\xi + \delta_1 \\ X_2 & = \lambda_2\xi + \delta_2 \\ X_3 & = \lambda_3\xi + \delta_3 \\ X_4 & = \lambda_4\xi + \delta_4 \\ X_5 & = \lambda_5\xi + \delta_5 \\ \end{aligned}$$在这些公式中,系数 $\lambda$反映了潜在公因子 $\xi$体现在测量值 $X$中的程度。 假设 $X$和 $\xi$都是标准化的变量(即有零均值和单位方差), $X_i$的方差可以被分解为:$$\mathrm{var}{X_i} = \mathrm{var}{\lambda_i\xi + \delta_i} = \lambda_i^2 + \mathrm{var}{\delta_i} = 1$$由于变量被标准化,$\lambda_i$可以解释为相关系数。 $\lambda_i^2$可被解释为 $X_i$中的变化在公因子 $\xi$反映的比例,被称作 $X_i$的。$X_i$中剩余的变化可由特殊因子 $\delta_i$解释。 如果用$\theta_{ii} = \mathrm{var}{\delta_i}$来表示特殊因子的方差(我们假设它反映了$X_i$的计量误差), 那么 $X_i$的共同度等于$$1 - \theta_{ii}^2$$</p><p>如果 $X_i$的共同度接近 1 (即误差方差接近 0),表示 $X_i$是潜在公因子$\xi$的一个几乎完美的度量。 反之,如果共同度接近 0,表示 $\xi$完全没有体现在 $X_i$中 (如果 $X_i$是一个对学生智力设计较差的考试,这种情况就可能发生), 那么系数$\lambda$可能接近零,并且几乎 $X_i$的所有方差都由特殊因子 $\delta_i$解释。</p><p>在这个特殊的示例中, 我们从一个潜在于 Holzinger 和 Swineford的研究中考试得分的单共同因子假设开始(一种类似于通用智力的东西)。但是,也有一种情况,即考试中的个人表现可能是多于一个潜在能力的函数。例如,我们可能认为学生的考试表现有两个不同的内在能力所决定——文学天赋(记为 $\xi_1$)和计量天赋(记为 $\xi_2$)——并且这两种不同的因子在不同类型的考试中有不同程度的表现。这个双因子模型如图所示。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/理论/exploratory-factor-analysis/path-diagram-of-two-factor-model-with-five-variables.png"><p>在双因子模型中,在每个考试得分度量 $X_i$中有三个源贡献了变异:两个公因子 $\xi_1$和 $\xi_2$以及一个特殊变量 $\delta_i$。使用公式的形式,双因子模型可以写作: $$\begin{aligned} X_1 & = \lambda_{11}\xi_1 + \lambda_{12}\xi_2 + \delta_1 \\ X_2 & = \lambda_{21}\xi_1 + \lambda_{22}\xi_2 + \delta_2 \\ X_3 & = \lambda_{31}\xi_1 + \lambda_{32}\xi_2 + \delta_3 \\ X_4 & = \lambda_{41}\xi_1 + \lambda_{42}\xi_2 + \delta_4 \\ X_5 & = \lambda_{51}\xi_1 + \lambda_{52}\xi_2 + \delta_5 \\\end{aligned}$$</p><p>如前,系数 $\lambda$反映了潜在公因子 $\xi$体现在测量值 $X$中的程度。如果 $X$和 $\xi$被标准化,参数 $\lambda$可以被解释为相关系数。如果我们进一步假设潜在因子互不相关(公因子模型的另一个标准假设),那么每个考试度量的共同度由那个变量因子载荷的平方和给出; 因此,$X_i$的共同度由 $\lambda_{11}^2 + \lambda_{12}^2$给出。</p><p>考虑在双因子模型下,如果一个学生有较高的文学天赋(高$\xi_1$)和较低的计量天赋(低 $\xi_2$),那会发生什么。我们可以预期这个学生在那些比计量天赋更需要文学天赋的考试中表现良好。如果学生在句子完成考试中的表现只依赖于他(她)的文学天赋,那么我们应该期望一个接近于 1 的 $\lambda_{11}$的值和一个接近于 0 的 $\lambda_{12}$的值。</p><p>在没有较强的关于学生考试表现潜在因子结构的先验知识这个探索性设定中,我们希望能够从数据中能够推到出潜在因子的合理数量以及公因子模型公式中的系数值。这个方法(在下一节中会详细阐述)和主成分分析的方法相类似,都是尝试提取一个较少数量的因子组以充分代表观测值的相关矩阵。不同的是,公因子模型中,我们必须解释特殊因子 $\delta$,而这没有在主成分分析中体现。</p><h2 id="求解过程"><a href="#求解过程" class="headerlink" title="求解过程"></a>求解过程</h2><p>和主成分分析一样,探索性因子分析的求解过程主要关注于 $\mathbf{X}$的协方差矩阵或相关系数矩阵的分解。这两种方法的不同之处在于,特殊因子是公因子模型的一部分。由于这些特殊因子被认为彼此不相关,而且与潜在公因子独立,这些因子仅仅贡献于协方差矩阵的对角线元素。这一点通过检查每个度量值 $X_i$协方差矩阵的对角线元素可以很容易发现:$$\mathrm{var}{X_i} = \mathrm{var}{\lambda_{i1}\xi_1 + \lambda_{i2}\xi_2 + \delta_i}$$</p><p>$X_i$方差表达式中有九个交叉项。根据公因子模型加入的独立性假设(以及进一步的公因子被标准化为具有单位方差的假设),所有的协方差项都排除在模型之外,只剩$$\mathrm{var}{X_i} = \lambda_{i1}^2 + \lambda_{i2}^2 + \theta_{ii}^2$$其中$\theta_{ii}^2 = \mathrm{var}{\delta_i}$。 因此,特殊因子出现在 $\mathbf{X}$的协方差矩阵中的唯一位置就是对角线,也就是他们对每个度量贡献变异的地方。</p><p>如果我们实现直到每个特殊因子的变异呢?如果这样,那么我们可以在协方差矩阵中减去这些值。然后我们剩下一个矩阵,仅由潜在公因子的方差和协方差构成。然后我们可以使用主成分分析方法来分解这些矩阵,并寻找公因子。这是我们在探索性因子分析中使用的求解过程的本质。在主成分分析中,我们分解相关矩阵 $\mathbf{R}$(对角线元素都是 1)。但是对于公因子模型,我们分解对角线元素为 $1 - \theta_{ii}^2$的相关矩阵。 实际上,我们通过在模型中减去由于特殊因子引起的变异开始(回想一下,这些变异可以被解释为测量误差或其他针对于不同考试的变异源,而且和其他因子不相关),仅在对角线上保留共同度。 然后我们尝试使用公因子解释剩下的变异。如果我们知道共同度,那么我们就可以使用与主成分分析相同的方法解决这个问题。这有时被称为(或简单的被称为公因子模型的主成分方法)。共同度的估计是这个求解过程中非平凡的部分,有许多不同的方法。</p><p>现在让我们假设我们不知道在我们这个解释性示例中由于特殊因子引起的变异的大小(在后续章节中,我们讨论当我们不知道这些值时该怎么办)。对于这个考试集合,我们假设每个考试中大约一半的变异都有特殊因子产生,也就是说,我们开始时对所有 $i$设置 $\theta_{ii}^2 = 0.50$然后对这个修改的矩阵进行矩阵分解。 特征值如下所示:$$\begin{array}{ccccc} \lambda_1 = 2.187 & \lambda_2 = 1.022 & \lambda_3 = -0.135 & \lambda_4 = -0.089 & \lambda_5 = 0.015 \end{array}$$</p><p>首先值得注意的是,这些特征值和主成分分析的特征值有所不同。并不是所有的特征值都是正的,并且他们的和也不是 $p$(分析中变量的数目)。原因是,通过减去由于特殊因子引起的变异,我们减少了需要由公因子解释的剩余的信息。这个模型中已经由特殊因子解释的变异是$0.50 + 0.50 + 0.50 + 0.50 + 0.50 = 2.5$, 即 $2.5/5.0 = 50\%$的原始五个考试得分的变异。 对角线元素的和也是$2.5$,即由公因子解释的方差。</p><p>现在仍然存在问题:我们需要多少公因子?注意到选择提取的因子数量的标准与主成分分析相比多少有点不同变化。由于有一些解释了一部分变异的特殊因子,我们的目标是使用公因子解释尽可能多的剩余变异。因此我们寻找有意义地不同于零地特征值。 在这种情况下,是 2,表示我们提取$c = 2$个公因子。</p><p>双因子解的矩阵(即有原始变量和公因子地相关系数组成地矩阵)如表所示。这种因子结构的模式表现出前三个考试(篇章理解、句子完成、单词含义)在第一个公因子上载荷更多(所有相关系数都接近0.8),而后两个考试(加法和计数)在第二个公因子上载荷更高(所有相关系数都接近0.6)。这和第一个因子反映学生的文学天赋、第二个因子指示计量天赋的解释一致。</p><table><thead><tr><th></th><th>Factor 1</th><th>Factor 2</th></tr></thead><tbody><tr><td>$PARA$</td><td>$0.7722$</td><td>$-0.2351$</td></tr><tr><td>$SENT$</td><td>$0.7838$</td><td>$-0.1576$</td></tr><tr><td>$WORD$</td><td>$0.7562$</td><td>$-0.2372$</td></tr><tr><td>$ADD$</td><td>$0.4293$</td><td>$0.6017$</td></tr><tr><td>$DOTS$</td><td>$0.3476$</td><td>$0.6506$</td></tr></tbody></table><p>使用因子载荷,我们也可以通过这两个公因子计算每项考试解释的变异所占的比例。这个比例被称为。 例如,对于篇章理解考试($X_1$),公因子解释了考试中$(0.77)^2 + (-0.24)^2 = 0.65$即 65% 的变异。这个共同度值比我们一开始在分析中使用的 $1 - \theta_{11}^2 = 0.50$的值略高。 这是因为我们的起点是基于一些先验知识,只是近似的。对于学生的特定样本,精确值更可能是不同的值。</p><p>通过跌多过程来修正我们初始估计值是可行的,最终我们会得到一个一致的结果。我们用第一次因子分析的结果替代我们处是共同度估计值,并再次进行因子分析。那么,我们将使用新的值 0.65 代替 $X_1$(篇章理解) 对应的对角线上的元素0.50, 对于其他变量我们也这么做。我们可以继续一次迭代、两次迭代这个过程,直到它收敛——也就是说,直到两次迭代之间共同度的变化足够小。这个迭代过程经常被用于共同度的估计。 这个方法有时被称为。</p><p>如果我们没有足够的关于我们数据中测量误差的先验知识(即我们的测量中与潜在因子无关的非系统变异)呢?我们使用什么来设置共同度的初始估计值? 一个被广泛使用的方法是,即在数据集中一个变量中可被其他所有变量解释的方差的大小。例如,如果我们想使用多元相关平方系数作为作为 $X_1$(篇章理解)的初始估值, 我们将 $X_1$与其余的变量 $X_2, X_3, X_4, X_5$进行回归,并使用 值。</p><p>为什么多元相关平方系数是共同度的良好估值? 因为共同度是由公因子 $\xi$所解释的 $X$中方差的占比。 尽管我们因此喜欢用公因子 $\xi$对 $X$进行回归,但也仍然存在公因子不可被观测的问题。 但是我们有其余变量$X$,每个都反映了潜在变量 $\xi$(尽管并不完美)。因为测量有误差,他们解释 $X$的能力被减弱了。因此,多元相关平方系数可以作为共同度的下界。一般地,测量越可靠(即特殊因子有较小的方差),多元相关平方系数作为共同度的估值越准确。</p><p>表展示了考试得分数据使用多元相关平方系数作为共同度估值的因子分析结果。我们使用一个像上述的迭代过程:用修正的共同度估值代替初始值并继续,直到后续的共同度估计值没有明显改变。</p><p>表所示的初始的因子载荷和表所示的最终结果的差异并不显著。因子模式指向相同的因子本质的解释。变异的划分也基本相同:由共同因子解释的变异的大小仍然大约是 66%。对比两个初始估计值,最后两个考试(加法和计数)相关的共同度比前三个低:对于 $X_4$和 $X_5$,共同度大约是 0.60,而对于 $X_1$,$X_2$和 $X_3$共同度大约是 0.70。如果我们认为这种差异来自于测量误差,那么我们可能得出后两个考试比前两个考试更不可靠的结论。</p><p>关于这种共同度的估计方法,需要注意一点。如果技术考试被省略,仅保留加法作为学生计量天赋的唯一度量,会怎么样?在这些情况下,基于多元相关平方系数计算的假发考试的共同度的估值会非常低(因为前三个考试没有任何一个反映第二个共同因子)。因此,我们更可能得出加法是一项非常不可靠的考试,但实际上它是一个重要潜在结构的唯一度量。正是共同度估值的这个问题,导致有人更喜欢主成分分析法而不是因子分析法。</p><h3 id="旋转因子解"><a href="#旋转因子解" class="headerlink" title="旋转因子解"></a>旋转因子解</h3><p>在下面第<a href="#严密推导">严密推导</a>节中我们会看到,公因子模型实际上拥有无穷多解,每种解都有平等的能力再现观测到的协方差矩阵。原因是因子解的方向(即描述坐标系统的基向量的选择)是非常随意的。这被称为公因子模型的。在主成分分析中,为了避免不确定性,我们将问题这样定义:第一主成分是原始数据(经过合理缩放)具有最大方差的线性组合,第二主成分是拥有次大方差(条件是与第一主成分不相关)的线性组合,等等。这保证了唯一(尽管有些随意)解。</p><p>如果因子解的方向是非常随意的,那么为什么不选择一个可以更好地帮助我们理解并解释数据的解?使用因子载荷矩阵尝试提出潜在公因子的一个明确解释往往是比较头疼的。如果我们可以通过旋转因子解选择一个不同的方向使载荷矩阵得到简化则是非常有利的。问题就是,如何选择这个方向?如何使变化因子载荷矩阵的目标变得可操作,使我们可以找到让我们更接近那个目标的因子解的旋转方式?</p><p>寻找这个旋转矩阵$\mathbf{T}$(我们暂时将讨论限制在正交旋转矩阵范围内,这保留了潜在公因子的独立性)最流行的方法是基于 Thurstone (1947)描述简单结构原则。 Thurstone认为大多数内容域可能涉及几个隐藏的(即潜在的或不可观测的)因子。他同样假设任何一个观测到的变量可能和一个或几个潜在因子相联系,而且任何一个因子可能仅和几个变量相联系。那么最一般的想法,只要可能,就是找到几个变量簇,每个簇仅明确一个因子。更一般地,我们想要找到几个因子轴的方向,使得每项考试(或其他变量)尽在几个因子上有相对较高的载荷(正或负),而其他因子的载荷趋于零。</p><p>如果潜在简单结构的论证有效,那么因子载荷矩阵应该展现出一种特定的模式(Comery,1973):</p><ol><li>大多数特殊因子(列)的载荷应当很小(尽可能接近于零),仅仅一些载荷的绝对值应该很大。</li><li>载荷矩阵的某行,包括每个因子的给定变量的载荷,应当在一个或少数因子上表现出非零载荷。</li><li>任何一对因子(列)应当展现出不同的载荷模式。否则,这两列所表示的因子无法区分。</li></ol><p>一个可以表明简单结构概念的假设例子如表和图所示。设想一个对消费者对止痛药的看法的研究,其中要求每个受试者根据六个属性评价他(她)的首选止痛药品牌:</p><ol><li>不反胃</li><li>没有副作用</li><li>能止痛</li><li>起效快</li><li>能使人保持清醒</li><li>适度依赖性[^2]</li></ol><p>在表中第一部分所示的因子载荷矩阵是未经旋转的双因子分析的解。(回想一下,这些未旋转的因子通过使第一个公因子解释最多变异、第二个公因子在与第一个公因子物馆的条件下尽可能解释更多剩下的变异而定向。)注意到所有载荷矩阵的 12 个元素都相对较大(绝对值都大于 0.4)。有这么多重大交叉载荷,获得因子的简单解释就变得比较困难。</p><table><thead><tr><th></th><th>未旋转解</th><th></th><th>旋转的解</th><th></th></tr></thead><tbody><tr><td>特性</td><td>Factor 1</td><td>Factor 2</td><td>Factor 1</td><td>Factor 2</td></tr><tr><td>不反胃</td><td>$0.579$</td><td>$-0.452$</td><td>$0.139$</td><td>$0.721$</td></tr><tr><td>没有副作用</td><td>$0.522$</td><td>$-0.572$</td><td>$0.017$</td><td>$0.774$</td></tr><tr><td>能止痛</td><td>$0.645$</td><td>$0.436$</td><td>$0.772$</td><td>$0.097$</td></tr><tr><td>起效快</td><td>$0.542$</td><td>$0.542$</td><td>$0.764$</td><td>$-0.051$</td></tr><tr><td>能使人保持清醒</td><td>$-0.476$</td><td>$0.596$</td><td>$-0.034$</td><td>$-0.762$</td></tr><tr><td>适度依赖性</td><td>$-0.613$</td><td>$-0.439$</td><td>$-0.750$</td><td>$-0.074$</td></tr><tr><td></td><td>解释的方差</td><td></td><td>解释的方差</td><td></td></tr><tr><td></td><td>$1.921$</td><td>$1.562$</td><td>$1.765$</td><td>$1.718$</td></tr></tbody></table><p>图绘制出因子载荷,并展示旋转因子空间使特性更接近地位于公因子附近如何成为可能。想法是选择一个旋转角,使每个特性在公因子上地投影要么很大(即绝对值将近1)要么很小(绝对值接近 0)。这实际上减小了交叉载荷并在简单结构方向上移动了载荷矩阵。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/理论/exploratory-factor-analysis/plot-of-factor-loadings-for-attributes-of-pain-relievers1.png"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/理论/exploratory-factor-analysis/plot-of-factor-loadings-for-attributes-of-pain-relievers2.png"><p>表所示的旋转的因子载荷矩阵展示了接近简单结构的东西。特性 3 和 4(”能止痛”和”起效快”)在第一个公因子上有正载荷,特性 6(适度依赖性)有负载荷。 特性 1 和 2(”不反胃”和”没有副作用”)在第二个公因子上有正载荷,特性 5(”能保持清醒”)有负载荷。 所有其他的载荷的绝对值都很小。给定定义每个因子的属性簇,我们可以标记第一个因子为”有效性”和第二个因子”温和性”。(负载荷反映负相关。例如,”能保持清醒”的得分越高,”温和性”得分越低。)</p><p>需要注意的是,旋转过后,初始因子方向方差最大化的性质就丢失了;也就是说,虽然总体上保留的因子能够解释与原先的数据集中一样多的变异,但这种变异现在在旋转配置的新维度上有所不同。因此,第一个(旋转后的)因子解释最大的变异的情况不存在了。由于每个因子解释的变异的大小通常并不是关心的主要目标,如果旋转后的解更容易解释,那么这种取舍通常认为是值得的。</p><h2 id="严密推导"><a href="#严密推导" class="headerlink" title="严密推导"></a>严密推导</h2><p>在这个更形式化的因子分析模型的处理中,回到主成分分析模型的建立过程中是非常有用的。我们已经介绍了,对主成分分析问题的求解等同于对标准化的数据矩阵 $\mathbf{X}$进行奇异值分解,如下所示: $$\mathbf{X} = \mathbf{Z}<em>{\mathrm{s}} \mathbf{D}^{\frac{1}{2}}\mathbf{U}^{\mathrm{T}}$$其中$\mathbf{Z}</em>{\mathrm{s}}$是标准化的主成分(全都互不相关),$\mathbf{D}^{\frac{1}{2}}$是对角线元素全为主成分标准差的对角矩阵, $\mathbf{U}$是特征向量矩阵(全都互相正交)。 据此,我们可以将样本相关矩阵 $\mathbf{R}$重写为奇异值分解得到的特征值和特征向量的函数。 相关系数矩阵为$$\mathbf{R} = \frac{1}{n-1} \mathbf{X}^{\mathrm{T}} \mathbf{X}$$带入式中的奇异值分解结果到式中并简化可得 $$\begin{aligned} \mathbf{R} & = \frac{1}{n-1}(\mathbf{Z}<em>{\mathrm{s}} \mathbf{D}^{\frac{1}{2}}\mathbf{U}^{\mathrm{T}})^{\mathrm{T}}(\mathbf{Z}</em>{\mathrm{s}} \mathbf{D}^{\frac{1}{2}}\mathbf{U}^{\mathrm{T}}) \\ & = \frac{1}{n-1}\mathbf{U}\mathbf{D}^{\frac{1}{2}}(\mathbf{Z}<em>{\mathrm{s}}^{\mathrm{T}}\mathbf{Z}</em>{\mathrm{s}})\mathbf{D}^{\frac{1}{2}}\mathbf{U}^{\mathrm{T}} \\ & = (\mathbf{U}\mathbf{D}^{\frac{1}{2}})(\mathbf{U}\mathbf{D}^{\frac{1}{2}})^{\mathrm{T}}\end{aligned}$$因为$\frac{1}{(n-1)}\mathbf{Z}<em>{\mathrm{s}}^{\mathrm{T}}\mathbf{Z}</em>{\mathrm{s}}$正好是一个单位阵。如果我们继续回忆,矩阵乘积 $\mathbf{U}\mathbf{D}^{\frac{1}{2}}$正好是因子载荷矩阵$\mathbf{F}$(即原始数据矩阵 $\mathbf{X}$和主成分矩阵 $\mathbf{Z}$的相关系数矩阵),我们可以 $\mathbf{R}$的表达式简化为如下形式:$$\mathbf{R} = \mathbf{F}\mathbf{F}^{\mathrm{T}}$$</p><p>由于我们使用主成分分析的目标往往是降维,所以我们尝试提取由 $c$个成分组成的子集 (其中 $c < p$,$p$是 $\mathbf{X}$中变量的个数)以近似$\mathbf{R}$。 因此,在主成分中有 $$\mathbf{R} \approx \mathbf{F}_c\mathbf{F}_c^{\mathrm{T}}$$其中 $\mathbf{F}_c$仅由因子载荷矩阵 $\mathbf{F}$的前几列组成。</p><p>在探索性因子分析中,我们同样尝试近似相关矩阵$\mathbf{R}$,但是使用一个不同的模型。在式中不是将 $\mathbf{X}$的奇异值分解代入,而是使用上述的公因子模型。 有 $c$个公因子的一般化模型写作: $$\begin{aligned} X_1 & = \lambda_{11}\xi_{1} + \lambda_{12}\xi_{2} + \cdots + \lambda_{1c}\xi_{c} + \delta_1 \\ X_2 & = \lambda_{21}\xi_{1} + \lambda_{22}\xi_{2} + \cdots + \lambda_{2c}\xi_{c} + \delta_2 \\ X_3 & = \lambda_{31}\xi_{1} + \lambda_{32}\xi_{2} + \cdots + \lambda_{3c}\xi_{c} + \delta_3 \\ & \vdots \\ X_p & = \lambda_{p1}\xi_{1} + \lambda_{p2}\xi_{2} + \cdots + \lambda_{pc}\xi_{c} + \delta_p \\\end{aligned}$$使用矩阵的形式,公因子模型表示为:$$\begin{aligned} \mathbf{X} = \mathbf{\Xi}\mathbf{\Lambda}_c^{\mathrm{T}} + \mathbf{\Delta} \end{aligned}$$其中$\mathbf{\Xi} = [\xi_1, \xi_2, \cdots, \xi_c]$,$\mathbf{\Delta} = [\delta_1, \delta_2, \cdots, \delta_n]$,$\mathbf{\Lambda}_c$是一个 $p \times c$的系数矩阵。另外,我们使用下面三个关于公因子模型成分的假设</p><ol><li>公因子 $\xi$互不相关,且有单位方差$$\frac{1}{n-1}\mathbf{\Xi}^{\mathrm{T}}\mathbf{\Xi} = \mathbf{I}$$</li><li>特殊因子 $\delta$互不相关,且拥有对角化协方差矩阵$$\mathbf{\Theta} = \frac{1}{n-1}\mathbf{\Delta}^{\mathrm{T}}\mathbf{\Delta} = \mathrm{diag}(\theta_{11}, \theta_{22}, \cdots, \theta_{pp})$$</li><li>公因子 $\xi$和特殊因子 $\delta$互不相关 $$\mathbf{\Xi}^{\mathrm{T}}\mathbf{\Delta} = \mathbf{0}$$</li></ol><p>现在我们将公式$$\mathbf{R} = \frac{1}{n-1} \mathbf{X}^{\mathrm{T}} \mathbf{X}$$中的 $\mathbf{X}$用公式$$\mathbf{X} = \mathbf{\Xi}\mathbf{\Lambda}_c^{\mathrm{T}} + \mathbf{\Delta}$$代替, 来对相关系数矩阵 $\mathbf{R}$进行近似: $$\begin{aligned} \mathbf{R} & = \frac{1}{n-1}(\mathbf{\Xi}\mathbf{\Lambda}_c^{\mathrm{T}} + \mathbf{\Delta})^{\mathrm{T}}(\mathbf{\Xi}\mathbf{\Lambda}_c^{\mathrm{T}} + \mathbf{\Delta}) \\ & = \frac{1}{n-1}( \mathbf{\Lambda}_c\mathbf{\Xi}^{\mathrm{T}}\mathbf{\Xi}\mathbf{\Lambda}_c + \mathbf{\Delta}^{\mathrm{T}}\mathbf{\Xi}\mathbf{\Lambda}_c^{\mathrm{T}} + \mathbf{\Lambda}_c\mathbf{\Xi}^{\mathrm{T}}\mathbf{\Delta} + \mathbf{\Delta}^{\mathrm{T}}\mathbf{\Delta} )\end{aligned}$$基于公因子模型的假设3,上式圆括号中的第二项和第三项为零。 基于假设 1,第一项中的表达式$\frac{1}{(n-1)}\mathbf{\Xi}^{\mathrm{T}}\mathbf{\Xi}$可以替换为单位矩阵 $\mathbf{I}$。 基于假设2,最后一项的表达式变为 $\mathbf{\Theta}$。 通过这些简化,我们可以得到$$\mathbf{R} = \mathbf{\Lambda}_c\mathbf{\Lambda}_c^{\mathrm{T}} + \mathbf{\Theta}$$或者 $$\mathbf{R} - \mathbf{\Theta} = \mathbf{\Lambda}_c\mathbf{\Lambda}_c^{\mathrm{T}}$$</p><p>将主成分分析模型的公式$$\mathbf{R} \approx \mathbf{F}_c\mathbf{F}_c^{\mathrm{T}}$$和公因子模型的公式$$\mathbf{R} - \mathbf{\Theta} = \mathbf{\Lambda}_c\mathbf{\Lambda}_c^{\mathrm{T}}$$进行对比,可以发现这两个方法之间的相似性以及本质区别。两种情况下,我们都是对一个二次对称矩阵进行分解。 矩阵 $\mathbf{\Lambda}_c$事实上与矩阵 $\mathbf{F}_c$同构: 是因子载荷矩阵,其元素可被解释为原始变量$\mathbf{X}$和提取的 $c$个公因子的相关系数。</p><h3 id="旋转不确定性"><a href="#旋转不确定性" class="headerlink" title="旋转不确定性"></a>旋转不确定性</h3><p>在主成分分析中,我们按顺序方式选择每个成分,以解释原始数据中尽可能大的变异,条件是与所有先前选定的主成分不相关。这确保了一个唯一解,虽然在选择的方向上有点武断。但是在公因子模型中,我们没有强加这种约束。结果是,事实上有无穷多解,他们对矩阵 $\mathbf{R} - \mathbf{\Theta}$能够近似的程度是相同的。 我们将这个属性称为公因子模型的。</p><p>我们首先通过一个例子来展示这种不确定性。考虑表所示的考试的分数据的双因子解。图展示了由该表中因子载荷矩阵所绘制的图表。我们现在通过将他们顺时针旋转30°来改变因子的方向。旋转(通过矩阵乘法进行)保留了两个因子的正交性。由章节,我们知道进行正交旋转(二维)的矩阵具有以下形式:$$\mathbf{T} = \begin{pmatrix} \cos\alpha & -\sin\alpha \\ \sin\alpha & \cos\alpha \end{pmatrix}$$当旋转角 $\alpha = -30$度时,我们得到下列正交旋转矩阵 $\mathbf{T}$$$\mathbf{T} = \begin{pmatrix} 0.866 & 0.500 \\ -0.500 & 0.866\end{pmatrix}$$</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/理论/exploratory-factor-analysis/diagram-showing-clockwise-rotation.png"><p>通过改变代表公因子的轴的方向,我们也改变了因子载荷的值。我们可以计算新的载荷(记为 $\mathbf{\Lambda}_c^<em>$),正好是旋转后的因子$\mathbf{\Xi}\mathbf{T}$和原始变量 $\mathbf{X}$(由 $\mathbf{\Xi}\mathbf{\Lambda}_c^{\mathrm{T}} + \mathbf{\Delta}$给出)的相关系数,简化为$$\mathbf{\Lambda}_c^</em> = \frac{1}{n-1}(\mathbf{\Xi}\mathbf{\Lambda}_c^{\mathrm{T}}+\mathbf{\Delta})^{\mathrm{T}}\mathbf{\Xi}\mathbf{T}$$由于$\frac{1}{(n-1)}\mathbf{\Xi}^{\mathrm{T}}\mathbf{\Xi} = \mathbf{I}$且 $\mathbf{\Delta}^{\mathrm{T}}\mathbf{\Xi} = 0$。旋转后的因子载荷 $\mathbf{\Lambda}_c^*$如表中原始未旋转载荷的旁边所示。正如之前所说的,矩阵中的主要载荷(即那些拥有高绝对值的载荷)并没有发生显著改变:前三项考试在第一个因子的载荷和后两个在第二个因子的载荷。但是注意旋转后的解的交叉载荷改变了。前三项考试现在在第二个因子上由正载荷(而不是负载荷),而后两项考试则几乎完全负载在第二个因子上(而不是在两个因子上都有正载荷)。</p><p>关于旋转因子解有两个重要的性质。第一,尽管每个因子解释的方差发生了变化,两个因子解释的总方差仍然相同。因为未旋转的解由分解矩阵 $\mathbf{R} - \mathbf{\Theta}$获得,这个解的方向使第一个因子能够解释最多的变异、第二个因子能够解释剩下的变异。旋转改变了方向,使得由第一个因子解释的变异变小了。但是旋转仅改变公因子空间轴的方向,所以并不影响解释的总方差。</p><p>第二个旋转解的性质使共同度没有改变。通过从旋转的因子载荷重构矩阵$\mathbf{R} - \mathbf{\Theta}$可以很容易发现这一性质。 我们有 $$\begin{aligned} \mathbf{R} - \mathbf{\Theta} & = \mathbf{\Lambda}_c^<em>{\mathbf{\Lambda}_c^</em>}^{\mathrm{T}} \\ & = \mathbf{\Lambda}_c\mathbf{T}(\mathbf{\Lambda}_c\mathbf{T})^{\mathrm{T}} \\ & = \mathbf{\Lambda}_c\mathbf{T}\mathbf{T}^{\mathrm{T}}\mathbf{\Lambda}_c^{\mathrm{T}} \\\end{aligned}$$进行矩阵乘法,很容易发现 $\mathbf{T}\mathbf{T}^{\mathrm{T}} = \mathbf{I}$: $$\begin{pmatrix} \cos^2\alpha + \sin^2\alpha = 1 & -\cos\alpha\sin\alpha + \sin\alpha\cos\alpha = 0 \\ \sin\alpha\cos\alpha - \cos\alpha\sin\alpha = 0 & \cos^2\alpha + \sin^2\alpha = 1 \\ \end{pmatrix}$$更一般地,任何维度的任何正交旋转矩阵都有$\mathbf{T}\mathbf{T}^{\mathrm{T}} = \mathbf{I}$。这是因为矩阵转置实际上将轴按照反方向转了回去,得到了最初的方向。</p><p>共同度和被公因子解释的变异以及公因子模型的拟合都不受正交旋转的影响,这一重要结论在后面我们考虑增强因子解的可解释性时会非常有用。</p><h3 id="因子旋转"><a href="#因子旋转" class="headerlink" title="因子旋转"></a>因子旋转</h3><p>为了找到最合适的旋转,我们必须找到一种方法以目标函数的形式定量化表达通过简化结构我们想要什么。然后我们搜索所有可能的旋转角度,然后选择矩阵 $\mathbf{T}$使满足旋转后的因子载荷矩阵 $\mathbf{A} = \mathbf{\Lambda}_c\mathbf{T}$对于简化的结构展现出高的目标函数的值。有许多不同的方法来量化最简结构,我们只讨论两个: Kaiser的最大方差正交旋转法和四次方最大旋转法。</p><h4 id="Kaiser-的最大方差正交旋转法"><a href="#Kaiser-的最大方差正交旋转法" class="headerlink" title="Kaiser 的最大方差正交旋转法"></a>Kaiser 的最大方差正交旋转法</h4><p>回忆旋转后的载荷矩阵每个元素 $a_{ik}$可以被解释为变量 $i$和公因子 $k$间的相关系数。 载荷的平方 $a_{ik}^2$是变量 $i$中由公因子 $k$引起的变异的比例。由于我们所选择的公因子互不相关,所有公因子所能解释的变异大小之和——即所谓的共同度——可以由载荷平方和给出,也就是 $h_i^2 = \sum_k a_{ik}^2$。为了实现最简结构,我们想要找到一个旋转使得平方载荷 $a_{ik}^2$要么接近1,要么接近 0。</p><p>最大方差过程通过关注 $\mathbf{A}$的列来尝试实现这一目的:它选择旋转矩阵 $\mathbf{T}$来最大化 $a_{ik}^2$的列方差之和。 第 $k$个列方差由下式给出$$V_k = \frac{1}{p} \sum_{i=1}^{p}\left(a_{ik}^2\right)^2 - \frac{1}{p^2}\left(\sum_{i=1}^{p}a_{ik}^2\right)^2$$对所有因子 $k$最大化列方差 $V_k$之和等同于最大化下式$$V = \sum_{k=1}^{c}\sum_{i=1}^{p}a_{ik}^4 - \frac{1}{p}\sum_{k=1}^{c}\left(\sum_{i=1}^{p}a_{ik}^2\right)^2$$注意到当 $a_{ik}^2$的值趋向于 0 或 1 时可以得到最大方差;根据定义,对于某些因子 $k$,当 $a_{ik}^2$的值趋向于1,所有其他在矩阵中同一行上的项都趋于 0。</p><p>对于最大方差正交旋转法使用归一化平方载荷($\frac{a_{ik}^2}{h_i^2}$)建立目标函数也是可能的。如果使用这种方式归一化载荷,$\frac{a_{ik}^2}{h_i^2}$可以被解释为 变量$i$由于公因子 $k$引起变异大小的比例。使用这种归一化确保在选择旋转时所有矩阵由相同的权重(当一些变量有较低共同度时对于没有归一化的平方载荷则不是这种情况)。</p><h4 id="四次方最大旋转法"><a href="#四次方最大旋转法" class="headerlink" title="四次方最大旋转法"></a>四次方最大旋转法</h4><p>与最大方差正交旋转法关注旋转的因子载荷矩阵 $\mathbf{A}$的列不同,四次方最大旋转法关注行。四次方最大旋转法的目标函数依赖于正交旋转前后变量的共同度不变这一事实;因此,表达式 $\sum_k a_{ik}^2$是常数,与旋转矩阵 $\mathbf{T}$无关。对于所有变量,共同度的平方和 $\sum_i(\sum_k a_{ik}^2)^2$也是一个常数。扩展这个表达式得到$$\sum_{i=1}^{p}\sum_{k=1}^{c} a_{ik}^4 + \sum_{i=1}^{p}\left(\sum_{k=1}^{c}\sum_{j\neq k}a_{ik}^2a_{ij}^2\right)$$</p><p>上式的第二项是平方载荷的交叉积。当矩阵具有简单结构时,该积应当尽可能小(即当一个变量在因子 $k$上有高载荷,对其他所有因子 $j$载荷应当接近0)。 因为上式对于所有旋转矩阵 $\mathbf{T}$是常数,一种保证交叉积项最小的方法是最大化表达式的第一项。因此,四次方最大旋转法选择一个正交旋转矩阵 $\mathbf{T}$使$$Q = \sum_{i=1}^{p}\sum_{k=1}^{c} a_{ik}^4$$最大。与最大方差正交旋转法相同,在进行旋转之前,可以对平方载荷通过除以变量的共同度进行归一化。</p><p>我们在下面的章节中讨论倾斜旋转(旋转不能保持因子的相互正交性)的问题。</p><h3 id="因子得分"><a href="#因子得分" class="headerlink" title="因子得分"></a>因子得分</h3><p>通常,因子分析本身并不是目的,而是进一步分析数据的中间步骤。对于后续分析,我们需要获得在缩减的因子空间中每个原始观测的位置。这个值被称为。</p><p>从公因子模型中得到因子得分并不像从主成分分析中得到成分得分一样容易。回想一下,主成分得分是原始变量的线性组合,可以使用来自相关矩阵的特征向量的系数直接计算。在公因子分析中,由于特定因素引入的不确定性,得分无法准确计算。换句话说,在公因子模型中我们不能将 $\xi$在不知道 $\delta$的情况下写成$X$的函数。</p><p>因此,有必要估计我们将在计算因子得分时使用的因子得分系数。我们用下述线性表达式近似 $\mathbf{\Xi}$$$\mathbf{\Xi} = \mathbf{X}_s\mathbf{B}$$其中 $\mathbf{B}$时因子得分系数矩阵。由于 $\mathbf{\Xi}$的值不能直接被观测到,我们不能使用最小二乘回归计算 $\mathbf{B}$。但是,如果我们对上式两边同时乘以 $\frac{1}{(n-1)}\mathbf{X}_s^{\mathrm{T}}$我们可以得到$$\frac{1}{n-1}\mathbf{X}_s^{\mathrm{T}}\mathbf{\Xi} = \frac{1}{n-1}\mathbf{X}_s^{\mathrm{T}}\mathbf{X}_s\mathbf{B}$$或$$\mathbf{\Lambda}_c = \mathbf{R}\mathbf{B}$$</p><p>在保证等式不变的情况下,我们将两边同时乘以$\mathbf{R}^{-1}$,对于因子得分系数有 $$\mathbf{B} = \mathbf{R}^{-1}\mathbf{\Lambda}_c$$将这个式子中的 $\mathbf{B}$代入式中得到用于估计因子得分的下式$$\mathbf{\Xi} = \mathbf{X}_s \mathbf{R}^{-1} \mathbf{\Lambda}_c$$</p><p>注意到由于载荷矩阵 $\mathbf{\Lambda}_c$满足旋转不确定性,式得到的因子得分并不唯一,并且具有相同的旋转不确定性。但是乘积$\hat{\mathbf{X}}_c = \mathbf{\Xi}\mathbf{\Lambda}_c^{\mathrm{T}}$是不变的, 其中 $\hat{\mathbf{X}}_c$由 $c$个潜在因子拟合的 $\mathbf{X}$的一部分。</p><p>[^1]: 译者注:原文中没有明确表示”不同”的 $i$和 $j$,但此处确实应不相同。 若相同,则 $\mathrm{corr}(\delta_i,\delta_j) = 1$ 而不是 $0$。</p><p>[^2]: 原文:Provides limited relief.</p>]]></content>
<summary type="html">
<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>在之前的章节中,我们讨论了主成分分析——一种多元数据降维以及理解变量间关联模式的方法。
在这个章节中,我们讨论,这是一种类似的方法,
基于一
</summary>
<category term="理论" scheme="http://hpdell.github.io/categories/%E7%90%86%E8%AE%BA/"/>
<category term="统计学" scheme="http://hpdell.github.io/tags/%E7%BB%9F%E8%AE%A1%E5%AD%A6/"/>
</entry>
<entry>
<title>让 Windows 的 R 用上 CUDA</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/windows-r-cuda/"/>
<id>http://hpdell.github.io/编程/windows-r-cuda/</id>
<published>2019-08-12T21:08:25.000Z</published>
<updated>2022-04-14T16:50:55.581Z</updated>
<content type="html"><![CDATA[<p>R 是一个统计学经常用到的软件,提供了非常多的统计学函数。但是它是一个单线程解释语言,面对大数据量的时候,往往性能跟不上,可以利用 Rcpp 编写 C++ 包提供给 R 使用,可以大大提高性能。而对于大规模数据的处理,使用 CUDA 则是一个非常好的解决方案。在 Linux 和 macOS 下, CUDA 程序和 C++ 程序都使用 gcc 编译器,但是在 Windows 下,Rcpp 的包必须用 MinGW 编译器, CUDA 的包必须用 MSVC 编译器,需要一定的技巧才能让 R 用上 CUDA。</p><p>本文介绍 MSVC 包和 MinGW 包的混合编译,不仅适用于 R 语言,也不仅适用于 CUDA 程序,也适用于其他需要通过 MinGW 的程序调用 MSVC 程序的情况。</p><a id="more"></a><h1 id="MinGW-调用-MSVC-库函数的条件"><a href="#MinGW-调用-MSVC-库函数的条件" class="headerlink" title="MinGW 调用 MSVC 库函数的条件"></a>MinGW 调用 MSVC 库函数的条件</h1><p>MinGW 其实一直都是可以直接调用 MSVC 库函数的,只是这样的函数需要满足几个条件:</p><ul><li>MinGW 可以调用 MSVC 编译的动态库,但是不能调用 MSVC 编译的静态库,因为 MinGW 和 MSVC 中会引用同样的符号,但 MSVC 有的符号在 MinGW 中没有,调用 MSVC 静态库时需要加载 MSVC 的符号,导致冲突。</li><li>MSVC 编译的 DLL 必须导出 C 接口,否则 MinGW 中找不到符号,这是因为 C++ 会给函数名做修改以支持函数重载,但 MSVC 和 MinGW 对函数修改的方式不一样。也有人说时 <code>__cdecl__</code> 和 <code>__stdcall__</code> 的问题,但我试了一下不太行,还是找不到符号。既然时 C 接口,那么参数和返回值不能是 class ,只能用指向 class 类型的指针类型。</li><li>不能将 MinGW 中创建的指针传递到 MSVC 中进行操作,否则在 MSVC 中就相当于野指针。反之也不可以。因为 MinGW 和 MSVC 编译的库,内存地址是两套,指针不互通。</li></ul><p>在 R 中,一定会大量用到矩阵,一有矩阵那必然涉及到指针,而且一定是在 MinGW 函数中创建的指针。那么该怎么将矩阵传到 MSVC 的函数中呢?这其实非常类似于 CUDA 编程中的内存问题,我们只需要在两边分别开辟内存,然后将内存中的数据复制一下,相当于写一个 <code>cudaMalloc</code> 和 <code>cudaMemcpy</code> 。这样相当于在全局创建了很多的变量,如果使用一些方法将这些全局变量统一管理会更好。可以将这些全局变量保存在一个结构体中,工厂函数返回一个指向这个结构体的指针,并为这个结构体成员创建初始值。</p><p>也可以利用 C++ 类的封装我们要在 R 中调用的函数,将这个类继承自一个抽象类(所有函数都是纯虚函数),将接口类导出,同时导出一个 C 的工厂函数,返回指向这个接口类的指针,利用 MSVC 和 MinGW 虚表结构一致的特点,就可以在 MinGW 的 C++ 代码中使用这个接口类中的函数了(类没有虚析构函数)。将矩阵数据作为类的成员变量,利用 C++ 成员函数进行创建、赋值、销毁。</p><h1 id="非类的写法"><a href="#非类的写法" class="headerlink" title="非类的写法"></a>非类的写法</h1><p>首先演示一种纯 C 非类的写法。首先需要建立一个 VS 的 CUDA 工程,并设置该项目配置类型为“动态链接库”。项目目录如下:</p><ul><li><code>AddCUDA</code><ul><li><code>add.cpp</code></li><li><code>add.h</code></li><li><code>kernel.cu</code></li><li><code>kernel.h</code></li><li><code>AddCUDA.vcxproj</code></li></ul></li></ul><p>文件 <code>kernel.cu</code> 使用的 CUDA 函数是 VS 中 CUDA 工程自带的模板,做了一些修改,主要去掉了 goto 语句,并设置了核函数启动配置。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// kernel.cu</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"cuda_runtime.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"device_launch_parameters.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"kernel.h"</span></span></span><br><span class="line"></span><br><span class="line">__<span class="function">global__ <span class="keyword">void</span> <span class="title">addKernel</span><span class="params">(<span class="keyword">int</span> *c, <span class="keyword">const</span> <span class="keyword">int</span> *a, <span class="keyword">const</span> <span class="keyword">int</span> *b)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> i = threadIdx.x;</span><br><span class="line"> c[i] = a[i] + b[i];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Helper function for using CUDA to add vectors in parallel.</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">addWithCuda</span><span class="params">(<span class="keyword">int</span> *c, <span class="keyword">const</span> <span class="keyword">int</span> *a, <span class="keyword">const</span> <span class="keyword">int</span> *b, <span class="keyword">unsigned</span> <span class="keyword">int</span> size)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> *dev_a = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> *dev_b = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> *dev_c = <span class="number">0</span>;</span><br><span class="line"> cudaError_t cudaStatus;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Choose which GPU to run on, change this on a multi-GPU system.</span></span><br><span class="line"> cudaStatus = cudaSetDevice(<span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (cudaStatus != cudaSuccess) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"cudaSetDevice failed! Do you have a CUDA-capable GPU installed?"</span>);</span><br><span class="line">cudaFree(dev_c);</span><br><span class="line">cudaFree(dev_a);</span><br><span class="line">cudaFree(dev_b);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Allocate GPU buffers for three vectors (two input, one output) .</span></span><br><span class="line"> cudaStatus = cudaMalloc((<span class="keyword">void</span>**)&dev_c, size * <span class="keyword">sizeof</span>(<span class="keyword">int</span>));</span><br><span class="line"> <span class="keyword">if</span> (cudaStatus != cudaSuccess) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"cudaMalloc failed!"</span>);</span><br><span class="line">cudaFree(dev_c);</span><br><span class="line">cudaFree(dev_a);</span><br><span class="line">cudaFree(dev_b);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> cudaStatus = cudaMalloc((<span class="keyword">void</span>**)&dev_a, size * <span class="keyword">sizeof</span>(<span class="keyword">int</span>));</span><br><span class="line"> <span class="keyword">if</span> (cudaStatus != cudaSuccess) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"cudaMalloc failed!"</span>);</span><br><span class="line">cudaFree(dev_c);</span><br><span class="line">cudaFree(dev_a);</span><br><span class="line">cudaFree(dev_b);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> cudaStatus = cudaMalloc((<span class="keyword">void</span>**)&dev_b, size * <span class="keyword">sizeof</span>(<span class="keyword">int</span>));</span><br><span class="line"> <span class="keyword">if</span> (cudaStatus != cudaSuccess) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"cudaMalloc failed!"</span>);</span><br><span class="line">cudaFree(dev_c);</span><br><span class="line">cudaFree(dev_a);</span><br><span class="line">cudaFree(dev_b);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Copy input vectors from host memory to GPU buffers.</span></span><br><span class="line"> cudaStatus = cudaMemcpy(dev_a, a, size * <span class="keyword">sizeof</span>(<span class="keyword">int</span>), cudaMemcpyHostToDevice);</span><br><span class="line"> <span class="keyword">if</span> (cudaStatus != cudaSuccess) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"cudaMemcpy failed!"</span>);</span><br><span class="line">cudaFree(dev_c);</span><br><span class="line">cudaFree(dev_a);</span><br><span class="line">cudaFree(dev_b);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> cudaStatus = cudaMemcpy(dev_b, b, size * <span class="keyword">sizeof</span>(<span class="keyword">int</span>), cudaMemcpyHostToDevice);</span><br><span class="line"> <span class="keyword">if</span> (cudaStatus != cudaSuccess) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"cudaMemcpy failed!"</span>);</span><br><span class="line">cudaFree(dev_c);</span><br><span class="line">cudaFree(dev_a);</span><br><span class="line">cudaFree(dev_b);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Launch a kernel on the GPU with one thread for each element.</span></span><br><span class="line">dim3 blockSize(256), gridSize((size + blockSize.x - 1) / blockSize.x);</span><br><span class="line"> addKernel<<<gridSize, blockSize >>>(dev_c, dev_a, dev_b);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Check for any errors launching the kernel</span></span><br><span class="line"> cudaStatus = cudaGetLastError();</span><br><span class="line"> <span class="keyword">if</span> (cudaStatus != cudaSuccess) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"addKernel launch failed: %s\n"</span>, cudaGetErrorString(cudaStatus));</span><br><span class="line">cudaFree(dev_c);</span><br><span class="line">cudaFree(dev_a);</span><br><span class="line">cudaFree(dev_b);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// cudaDeviceSynchronize waits for the kernel to finish, and returns</span></span><br><span class="line"> <span class="comment">// any errors encountered during the launch.</span></span><br><span class="line"> cudaStatus = cudaDeviceSynchronize();</span><br><span class="line"> <span class="keyword">if</span> (cudaStatus != cudaSuccess) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"cudaDeviceSynchronize returned error code %d after launching addKernel!\n"</span>, cudaStatus);</span><br><span class="line">cudaFree(dev_c);</span><br><span class="line">cudaFree(dev_a);</span><br><span class="line">cudaFree(dev_b);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Copy output vector from GPU buffer to host memory.</span></span><br><span class="line"> cudaStatus = cudaMemcpy(c, dev_c, size * <span class="keyword">sizeof</span>(<span class="keyword">int</span>), cudaMemcpyDeviceToHost);</span><br><span class="line"> <span class="keyword">if</span> (cudaStatus != cudaSuccess) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"cudaMemcpy failed!"</span>);</span><br><span class="line">cudaFree(dev_c);</span><br><span class="line">cudaFree(dev_a);</span><br><span class="line">cudaFree(dev_b);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>将 <code>addWithCuda</code> 函数的声明移动到 <code>kernel.h</code> 文件中。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// kernel.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><cuda_runtime.h></span></span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">addWithCuda</span><span class="params">(<span class="keyword">int</span> *c, <span class="keyword">const</span> <span class="keyword">int</span> *a, <span class="keyword">const</span> <span class="keyword">int</span> *b, <span class="keyword">unsigned</span> <span class="keyword">int</span> size)</span></span>;</span><br></pre></td></tr></table></figure><p>然后需要编写 MSVC 的接口函数了。在 <code>add.h</code> 文件中做如下声明:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// add.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> DLL_EXPORT</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ADDCUDA_API extern <span class="meta-string">"C"</span> __declspec(dllexport)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ADDCUDA_API extern <span class="meta-string">"C"</span> __declspec(dllimport)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">// DLL_EXPORT</span></span></span><br><span class="line"></span><br><span class="line"><span class="function">ADDCUDA_API <span class="keyword">int</span>* <span class="title">createVector</span><span class="params">(<span class="keyword">int</span> n)</span></span>;</span><br><span class="line"><span class="function">ADDCUDA_API <span class="keyword">void</span> <span class="title">setVector</span><span class="params">(<span class="keyword">int</span>* ptr, <span class="keyword">int</span> i, <span class="keyword">int</span> value)</span></span>;</span><br><span class="line"><span class="function">ADDCUDA_API <span class="keyword">int</span> <span class="title">getVector</span><span class="params">(<span class="keyword">int</span>* ptr, <span class="keyword">int</span> i)</span></span>;</span><br><span class="line"><span class="function">ADDCUDA_API <span class="keyword">void</span> <span class="title">deleteVector</span><span class="params">(<span class="keyword">int</span>* ptr)</span></span>;</span><br><span class="line"><span class="function">ADDCUDA_API <span class="keyword">bool</span> <span class="title">addVector</span><span class="params">(<span class="keyword">int</span>* a, <span class="keyword">int</span>* b, <span class="keyword">int</span> n, <span class="keyword">int</span> *c)</span></span>;</span><br></pre></td></tr></table></figure><p>当然, <code>__declspec(dllimport)</code> 可要可不要,要也可以,这样这个 Dll 也可以给 Windows 程序使用。</p><p>在 <code>add.cpp</code> 中进行定义:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// add.cpp</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"add.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"kernel.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span>* <span class="title">createVector</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">deleteVector</span><span class="params">(<span class="keyword">int</span>* ptr)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">delete</span>[] ptr;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">setVector</span><span class="params">(<span class="keyword">int</span>* ptr, <span class="keyword">int</span> i, <span class="keyword">int</span> value)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">ptr[i] = value;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">getVector</span><span class="params">(<span class="keyword">int</span>* ptr, <span class="keyword">int</span> i)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">return</span> ptr[i];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">addVector</span><span class="params">(<span class="keyword">int</span>* a, <span class="keyword">int</span>* b, <span class="keyword">int</span> n, <span class="keyword">int</span> *c)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">bool</span> cudaStatus = addWithCuda(c, a, b, n);</span><br><span class="line"><span class="keyword">return</span> cudaStatus;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>虽然这几个函数很短小,但是也不能写在头文件里。否则 MinGW 中会报符号二义性错误。</p></blockquote><p>然后在 MinGW 的主函数中进行调用</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// mingw.cpp</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"add.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> <span class="keyword">const</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> n = <span class="number">100000</span>;</span><br><span class="line"> <span class="keyword">int</span> *a = createVector(n);</span><br><span class="line"> <span class="keyword">int</span> *b = createVector(n);</span><br><span class="line"> <span class="keyword">int</span> *c = createVector(n);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">size_t</span> i = <span class="number">0</span>; i < n; i++)</span><br><span class="line"> {</span><br><span class="line"> setVector(a, i, <span class="number">10</span>);</span><br><span class="line"> setVector(b, i, <span class="number">100</span>);</span><br><span class="line"> setVector(c, i, <span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> addVector(a, b, n, c);</span><br><span class="line"> <span class="keyword">int</span> *result = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">size_t</span> i = <span class="number">0</span>; i < n; i++)</span><br><span class="line"> {</span><br><span class="line"> result[i] = getVector(c, i);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"result:\n"</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">size_t</span> i = <span class="number">0</span>; i < <span class="number">10</span>; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%5d"</span>, result[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"\n"</span>);</span><br><span class="line"> </span><br><span class="line"> deleteVector(a);</span><br><span class="line"> deleteVector(b);</span><br><span class="line"> deleteVector(c);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>MinGW 在链接时,需要手动指定 MSVC 生成的 lib 文件,而且要放到 <code>-o</code> 参数的后面,方法如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">g++ -I<span class="string">"./AddCUDA"</span> -L<span class="string">"./x64/Release"</span> mingw.o -o cudaMinGWC -lAddCUDA</span><br></pre></td></tr></table></figure><p>这样就生成了 <code>cudaMinGWC.exe</code> 文件。运行一下可以得到结果:</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">result: </span><br><span class="line"> 110 110 110 110 110 110 110 110 110 110</span><br></pre></td></tr></table></figure><h1 id="抽象类的写法"><a href="#抽象类的写法" class="headerlink" title="抽象类的写法"></a>抽象类的写法</h1><p>抽象类的写法主要用到了多态的特性。首先需要创建一个抽象基类 <code>IAdd</code> 和派生类 <code>CAdd</code> 。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// IAdd.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> DLL_EXPORT</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ADDCUDA_API __declspec(dllexport)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ADDCUDA_API __declspec(dllimport)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">// DLL_EXPORT</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ADDCUDA_API</span> <span class="title">IAdd</span></span></span><br><span class="line"><span class="class">{</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">SetA</span><span class="params">(<span class="keyword">int</span> i, <span class="keyword">int</span> value)</span> </span>= <span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">SetB</span><span class="params">(<span class="keyword">int</span> i, <span class="keyword">int</span> value)</span> </span>= <span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">int</span> <span class="title">GetC</span><span class="params">(<span class="keyword">int</span> i)</span> </span>= <span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">Add</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> <span class="function">ADDCUDA_API IAdd* <span class="title">Add_new</span><span class="params">(<span class="keyword">int</span> n)</span></span>;</span><br><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span> <span class="function">ADDCUDA_API <span class="keyword">void</span> <span class="title">Add_del</span><span class="params">(IAdd* ptr)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// IAdd.cpp</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"IAdd.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"CAdd.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="function">IAdd* <span class="title">Add_new</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> CAdd(n);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Add_del</span><span class="params">(IAdd* ptr)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">delete</span> ptr;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在抽象类中完全不实现类的任何接口,都标记为纯虚函数。同时,在抽象类的外面,定义一套工厂函数,用于创建和销毁这个抽象类派生类的对象。</p><p>派生类的定义如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CAdd.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"IAdd.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CAdd</span> :</span> <span class="keyword">public</span> IAdd</span><br><span class="line">{</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="keyword">int</span> n;</span><br><span class="line"><span class="keyword">int</span>* a;</span><br><span class="line"><span class="keyword">int</span>* b;</span><br><span class="line"><span class="keyword">int</span>* c;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">CAdd(<span class="keyword">int</span> n);</span><br><span class="line">~CAdd();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">SetA</span><span class="params">(<span class="keyword">int</span> i, <span class="keyword">int</span> value)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">SetB</span><span class="params">(<span class="keyword">int</span> i, <span class="keyword">int</span> value)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">int</span> <span class="title">GetC</span><span class="params">(<span class="keyword">int</span> i)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">bool</span> <span class="title">Add</span><span class="params">()</span></span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// CAdd.cpp</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"CAdd.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"kernel.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><memory.h></span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">CAdd::CAdd(<span class="keyword">int</span> n)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">this</span>->n = n;</span><br><span class="line">a = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line">b = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line">c = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line"><span class="built_in">memset</span>(a, <span class="number">0</span>, <span class="keyword">sizeof</span>(<span class="keyword">int</span>) * n);</span><br><span class="line"><span class="built_in">memset</span>(b, <span class="number">0</span>, <span class="keyword">sizeof</span>(<span class="keyword">int</span>) * n);</span><br><span class="line"><span class="built_in">memset</span>(c, <span class="number">0</span>, <span class="keyword">sizeof</span>(<span class="keyword">int</span>) * n);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">CAdd::~CAdd()</span><br><span class="line">{</span><br><span class="line"><span class="keyword">delete</span>[] a;</span><br><span class="line"><span class="keyword">delete</span>[] b;</span><br><span class="line"><span class="keyword">delete</span>[] c;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> CAdd::SetA(<span class="keyword">int</span> i, <span class="keyword">int</span> value)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">if</span> (i < n) a[i] = value;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> CAdd::SetB(<span class="keyword">int</span> i, <span class="keyword">int</span> value)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">if</span> (i < n) b[i] = value;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> CAdd::GetC(<span class="keyword">int</span> i)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">return</span> (i < n) ? c[i] : <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">bool</span> CAdd::Add()</span><br><span class="line">{</span><br><span class="line"><span class="keyword">return</span> addWithCuda(c, a, b, n);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在 MinGW 中调用如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"IAdd.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> <span class="keyword">const</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">int</span> n = <span class="number">100000</span>;</span><br><span class="line"> IAdd* ptr = Add_new(n);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">size_t</span> i = <span class="number">0</span>; i < n; i++)</span><br><span class="line"> {</span><br><span class="line"> ptr->SetA(i, <span class="number">10</span>);</span><br><span class="line"> ptr->SetB(i, <span class="number">100</span>);</span><br><span class="line"> }</span><br><span class="line"> ptr->Add();</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"result:\n"</span>);</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">size_t</span> i = <span class="number">0</span>; i < <span class="number">10</span>; i++)</span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%5d"</span>, ptr->GetC(i));</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"\n"</span>);</span><br><span class="line"></span><br><span class="line"> Add_del(ptr);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>程序运行结果:</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">result:</span><br><span class="line"> 110 110 110 110 110 110 110 110 110 110</span><br></pre></td></tr></table></figure><p>可见已经可以运行了。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>总体而言,这种方式调用方式的开销还是比较大的。不仅在内存中复制了一份数据,在传递数据的过程中是一个一个传递的,比直接内存拷贝开销大很多。另外如果采用抽象类的方式,还有虚函数调用的开销。因此,如果不是必须在 Windows 上用 MinGW 调用 CUDA 程序,尽量还是使用 MSVC 编译器。</p><p>对于 Rcpp 而言,恰恰是必须在 Windows 使用 MinGW ,此使想调用 CUDA 程序,则需要通过这种方式。</p><p>本文所涉及代码已发布在 <a href="https://github.com/HPDell/MSVC-MinGW-CUDA" target="_blank" rel="noopener">GitHub</a> 上。</p>]]></content>
<summary type="html">
<p>R 是一个统计学经常用到的软件,提供了非常多的统计学函数。
但是它是一个单线程解释语言,面对大数据量的时候,往往性能跟不上,可以利用 Rcpp 编写 C++ 包提供给 R 使用,可以大大提高性能。
而对于大规模数据的处理,使用 CUDA 则是一个非常好的解决方案。
在 Linux 和 macOS 下, CUDA 程序和 C++ 程序都使用 gcc 编译器,
但是在 Windows 下,Rcpp 的包必须用 MinGW 编译器, CUDA 的包必须用 MSVC 编译器,需要一定的技巧才能让 R 用上 CUDA。</p>
<p>本文介绍 MSVC 包和 MinGW 包的混合编译,不仅适用于 R 语言,也不仅适用于 CUDA 程序,
也适用于其他需要通过 MinGW 的程序调用 MSVC 程序的情况。</p>
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="R" scheme="http://hpdell.github.io/tags/R/"/>
<category term="CUDA" scheme="http://hpdell.github.io/tags/CUDA/"/>
</entry>
<entry>
<title>HTML5 播放 RTSP 视频</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/html5-rtsp/"/>
<id>http://hpdell.github.io/编程/html5-rtsp/</id>
<published>2019-03-04T15:01:25.000Z</published>
<updated>2022-04-14T16:50:55.449Z</updated>
<content type="html"><![CDATA[<p>目前大多数网络摄像头都是通过 RTSP 协议传输视频流的,但是 HTML 并不标准支持 RTSP 流。除了 Firefox 浏览器可以直接播放 RTSP 流之外,几乎没有其他浏览器可以直接播放 RTSP 流。Electron 应用是基于 Chromium 内核的,因此也不能直接播放 RTSP 流。</p><p>在借助一定工具的情况下,可以实现在 Web 页面上播放 RTSP 流。本文介绍的方法可以应用于传统 Web 应用和 Electron 应用中,唯一的区别是将 Electron 应用的主进程当作传统 Web 应用的服务器。</p><h1 id="目前已有-RTSP-播放方案的对比"><a href="#目前已有-RTSP-播放方案的对比" class="headerlink" title="目前已有 RTSP 播放方案的对比"></a>目前已有 RTSP 播放方案的对比</h1><p>既然是做直播,就需要延迟较低。当摄像头掉线时,也应当有一定的事件提示。处于这两点,对目前已有的已经实现、无需购买许可证的 RTSP 播放方案进行对比(处于原理阶段的暂时不分析)。</p><table><thead><tr><th>方案</th><th>协议</th><th>视频格式</th><th>延迟</th><th>离线事件汇报</th><th>最小端口占用</th><th>依赖</th></tr></thead><tbody><tr><td>1</td><td>HLS</td><td>ogg</td><td>网络延迟较高,可达10秒以上</td><td>难</td><td>n</td><td>VLC + video.js</td></tr><tr><td>2</td><td>RTMP</td><td>flv</td><td>网络延迟较低,5秒左右</td><td>难</td><td>n</td><td>ffmpeg + nginx + flash + video.js</td></tr><tr><td>3</td><td>WebSocket</td><td>mpegts</td><td>网络延迟很低,渲染速度慢</td><td>易</td><td>1</td><td>ffmpeg + express + jsmpeg</td></tr><tr><td>4</td><td>HTTP-FLV</td><td>flv</td><td>网络延迟很低,渲染速度快</td><td>易</td><td>1</td><td>ffmpeg + express + flv.js</td></tr></tbody></table><p>我对这四种方式都进行了实现,整体效果最好的还是第4种方案,占用端口少,延迟低,渲染速度快,而且离线事件易于处理。</p><h1 id="基于-flv-js-的-RTSP-播放方案"><a href="#基于-flv-js-的-RTSP-播放方案" class="headerlink" title="基于 flv.js 的 RTSP 播放方案"></a>基于 flv.js 的 RTSP 播放方案</h1><p><a href="https://github.com/bilibili/flv.js" target="_blank" rel="noopener">flv.js</a> 是 Bilibili 开源的一款 HTML5 浏览器。依赖于 Media Source Extension 进行视频播放,视频通过 HTTP-FLV 或 WebSocket-FLV 协议传输,视频格式需要为 FLV 格式。</p><h2 id="服务器端(主进程)"><a href="#服务器端(主进程)" class="headerlink" title="服务器端(主进程)"></a>服务器端(主进程)</h2><p>服务器端采用 express + express-ws 框架进行编写,当有 HTTP 请求发送到指定的地址时,启动 ffmpeg 串流程序,直接将 RTSP 流封装成 FLV 格式的视频流,推送到指定的 WebSocket 响应流中。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> express <span class="keyword">from</span> <span class="string">"express"</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> expressWebSocket <span class="keyword">from</span> <span class="string">"express-ws"</span>;</span><br><span class="line"><span class="keyword">import</span> ffmpeg <span class="keyword">from</span> <span class="string">"fluent-ffmpeg"</span>;</span><br><span class="line"><span class="keyword">import</span> webSocketStream <span class="keyword">from</span> <span class="string">"websocket-stream/stream"</span>;</span><br><span class="line"><span class="keyword">import</span> WebSocket <span class="keyword">from</span> <span class="string">"websocket-stream"</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> http <span class="keyword">from</span> <span class="string">"http"</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">localServer</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">let</span> app = express();</span><br><span class="line"> app.use(express.static(__dirname));</span><br><span class="line"> expressWebSocket(app, <span class="literal">null</span>, {</span><br><span class="line"> perMessageDeflate: <span class="literal">true</span></span><br><span class="line"> });</span><br><span class="line"> app.ws(<span class="string">"/rtsp/:id/"</span>, rtspRequestHandle)</span><br><span class="line"> app.listen(<span class="number">8888</span>);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"express listened"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">rtspRequestHandle</span>(<span class="params">ws, req</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"rtsp request handle"</span>);</span><br><span class="line"> <span class="keyword">const</span> stream = webSocketStream(ws, {</span><br><span class="line"> binary: <span class="literal">true</span>,</span><br><span class="line"> browserBufferTimeout: <span class="number">1000000</span></span><br><span class="line"> }, {</span><br><span class="line"> browserBufferTimeout: <span class="number">1000000</span></span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">let</span> url = req.query.url;</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"rtsp url:"</span>, url);</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"rtsp params:"</span>, req.params);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> ffmpeg(url)</span><br><span class="line"> .addInputOption(<span class="string">"-rtsp_transport"</span>, <span class="string">"tcp"</span>, <span class="string">"-buffer_size"</span>, <span class="string">"102400"</span>) <span class="comment">// 这里可以添加一些 RTSP 优化的参数</span></span><br><span class="line"> .on(<span class="string">"start"</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(url, <span class="string">"Stream started."</span>);</span><br><span class="line"> })</span><br><span class="line"> .on(<span class="string">"codecData"</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(url, <span class="string">"Stream codecData."</span>)</span><br><span class="line"> <span class="comment">// 摄像机在线处理</span></span><br><span class="line"> })</span><br><span class="line"> .on(<span class="string">"error"</span>, <span class="function"><span class="keyword">function</span> (<span class="params">err</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(url, <span class="string">"An error occured: "</span>, err.message);</span><br><span class="line"> })</span><br><span class="line"> .on(<span class="string">"end"</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(url, <span class="string">"Stream end!"</span>);</span><br><span class="line"> <span class="comment">// 摄像机断线的处理</span></span><br><span class="line"> })</span><br><span class="line"> .outputFormat(<span class="string">"flv"</span>).videoCodec(<span class="string">"copy"</span>).noAudio().pipe(stream);</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="built_in">console</span>.log(error);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当然这个实现还比较粗糙。当有多个相同地址的请求时,应当增加 ffmpeg 的输出,而不是启动一个新的 ffmpeg 进程串流。</p><h2 id="浏览器端(渲染进程)"><a href="#浏览器端(渲染进程)" class="headerlink" title="浏览器端(渲染进程)"></a>浏览器端(渲染进程)</h2><p>示例使用 Vue 框架进行页面设计。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><template></span><br><span class="line"> <div></span><br><span class="line"> <video class="demo-video" ref="player"></video></span><br><span class="line"> </div></span><br><span class="line"></template></span><br><span class="line"></span><br><span class="line"><script></span><br><span class="line">import flvjs from "flv.js";</span><br><span class="line">export default {</span><br><span class="line"> props: {</span><br><span class="line"> rtsp: String,</span><br><span class="line"> id: String</span><br><span class="line"> },</span><br><span class="line"> /**</span><br><span class="line"> * @returns {{player: flvjs.Player}}</span><br><span class="line"> */</span><br><span class="line"> data () {</span><br><span class="line"> return {</span><br><span class="line"> player: null</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> mounted () {</span><br><span class="line"> if (flvjs.isSupported()) {</span><br><span class="line"> let video = this.$refs.player;</span><br><span class="line"> if (video) {</span><br><span class="line"> this.player = flvjs.createPlayer({</span><br><span class="line"> type: "flv",</span><br><span class="line"> isLive: true,</span><br><span class="line"> url: `ws://localhost:8888/rtsp/${this.id}/?url=${this.rtsp}`</span><br><span class="line"> });</span><br><span class="line"> this.player.attachMediaElement(video);</span><br><span class="line"> try {</span><br><span class="line"> this.player.load();</span><br><span class="line"> this.player.play();</span><br><span class="line"> } catch (error) {</span><br><span class="line"> console.log(error);</span><br><span class="line"> };</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> },</span><br><span class="line"> beforeDestroy () {</span><br><span class="line"> this.player.destory();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></script></span><br><span class="line"></span><br><span class="line"><style></span><br><span class="line"> .demo-video {</span><br><span class="line"> max-width: 480px; </span><br><span class="line"> max-height: 360px;</span><br><span class="line"> }</span><br><span class="line"></style></span><br></pre></td></tr></table></figure><h1 id="效果展示"><a href="#效果展示" class="headerlink" title="效果展示"></a>效果展示</h1><p>用 Electron 页面展示了 7 个 Hikvison NVR 的摄像头,可以实现低延迟,低 CPU 占用,无花屏现象。由于涉及隐私,这里就不放截图了。</p><p>同样的方法我播放了 9 个本地 1080p 的视频《白鹿原》,可以看一下这个效果。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/html5-rtsp/bailuyuan-demo.png"><p>播放效果非常好,完全没有卡顿和花屏,CPU 占用率也不高。</p>]]></content>
<summary type="html">
<p>目前大多数网络摄像头都是通过 RTSP 协议传输视频流的,但是 HTML 并不标准支持 RTSP 流。除了 Firefox 浏览器可以直接播放 RTSP 流之外,几乎没有其他浏览器可以直接播放 RTSP 流。Electron 应用是基于 Chromium 内核的,因此也不能
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="网页开发" scheme="http://hpdell.github.io/tags/%E7%BD%91%E9%A1%B5%E5%BC%80%E5%8F%91/"/>
<category term="Node.js" scheme="http://hpdell.github.io/tags/Node-js/"/>
</entry>
<entry>
<title>Microsoft Machine Learning Server 使用调研</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/microsoft-machine-learning-server/"/>
<id>http://hpdell.github.io/编程/microsoft-machine-learning-server/</id>
<published>2019-03-04T15:01:25.000Z</published>
<updated>2022-04-14T16:50:55.521Z</updated>
<content type="html"><![CDATA[<h1 id="Machine-Learning-Server-主要功能"><a href="#Machine-Learning-Server-主要功能" class="headerlink" title="Machine Learning Server 主要功能"></a>Machine Learning Server 主要功能</h1><p>Micorosft Machine Learning Server 前身就是 Microsoft R Server ,加入了 Python 支持后更名。主要用于数据分析模型的商业化使用,并提供分布式、并行等计算功能。</p><p>支持以下平台:</p><ul><li>Windows</li><li>Hadoop</li><li>Linux</li></ul><p>Machine Learning Server 主要提供以下功能:</p><table><thead><tr><th>功能</th><th>R</th><th>Python</th></tr></thead><tbody><tr><td><strong>在 Machine Learning Server 中运行 R 代码</strong></td><td>o</td><td></td></tr><tr><td>使用 RevoScalePy 创建 Python 代码</td><td></td><td>o</td></tr><tr><td>使用 MicrosoftML 进行二分分类</td><td></td><td>o</td></tr><tr><td><strong>将模型作为 Web 服务发布</strong></td><td>o</td><td>o</td></tr></tbody></table><p>Machine Learning Server 的特性:</p><ul><li>无论您的数据存在于何处,都可以获得高性能的机器学习</li><li>微软和开源的最佳人工智能创新</li><li>简单,安全,高规模的操作和管理</li><li>深入的生态系统参与,以最佳的总体拥有成本实现客户成功</li></ul><p>Machine Learning Server 的使用是与 <strong>Microsoft R Client</strong> 结合进行的。Microsoft R Client 相当于一个 R 的发行版,内置了一些专用的函数,可以进行远程运行代码以及发布 Web 服务。</p><h1 id="RevoScaleR-库"><a href="#RevoScaleR-库" class="headerlink" title="RevoScaleR 库"></a>RevoScaleR 库</h1><p>提供了一系列 <code>rx</code> 开头的函数,这些函数可以与 Machine Learning Server 结合运行。</p><h2 id="数据处理"><a href="#数据处理" class="headerlink" title="数据处理"></a>数据处理</h2><p><code>rxImport</code> 从文本文件、 SAS 、 SPSS 、 SQL Server 、 Teradata 或 ODBC 连接导入数据。原生的数据类型是 XDF 格式。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">inDataFile <- file.path(rxGetOption(<span class="string">"sampleDataDir"</span>), <span class="string">"mortDefaultSmall2000.csv"</span>)</span><br><span class="line">mortOutput <- <span class="literal">NULL</span></span><br><span class="line">mortData <- rxImport(inData = inDataFile, outFile = mortOutput)</span><br></pre></td></tr></table></figure><blockquote><p><strong><em>关于 Machine Learning Server中的数据存储</em></strong></p><p>在 Machine Learning Server 中,您可以将内存数据用作数据框,或将其作为 XDF 文件保存到磁盘。如上所述, <code>rxImport</code> 可以使用或不使用 .xdf 文件创建数据源对象。如果省略 <code>outFile</code> 参数或将其设置为 <code>NULL</code>,则返回对象是包含数据的数据框。</p><p>数据框是 R 中的基本数据结构,在机器学习服务器中完全支持。它是表格,由行和列组成,其中列包含变量,第一行(称为标题)存储列名。后续行为与单个观察相关联的每个变量提供数据值。数据框是在加载某些数据时创建的临时数据结构。它仅在会话期间存在。</p><p>.xdf 文件是 Machine Learning Server 本机的二进制文件格式,用于在磁盘上保存数据。 .xdf 文件的组织是基于列的,每个变量一列,这对于统计和预测分析中使用的数据的可变方向是最佳的。使用 .xdf ,您可以加载数据的子集以进行目标分析。此外, .xdf 文件包括可立即使用的预先计算的元数据,无需额外处理。</p><p>不需要创建 .xdf ,但是当数据集很大或很复杂时, .xdf 文件可以通过压缩数据和将数据分配到可以独立读取和刷新的块来提供帮助。此外,在像 Hadoop 的 HDFS 这样的分布式文件系统上, XDF 文件可以将数据存储在多个物理文件中以容纳非常大的数据集。</p></blockquote><p><code>rxGetInfo</code> 一次快速获取有关数据集及其变量的信息,包括有关变量类型和范围的更多信息。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">rxGetInfo(mortData, getVarInfo = <span class="literal">TRUE</span>, numRows=<span class="number">3</span>)</span><br><span class="line"><span class="comment"># 输出</span></span><br><span class="line"><span class="comment"># Data frame: mortData</span></span><br><span class="line"><span class="comment"># Data frame: mortData</span></span><br><span class="line"><span class="comment"># Number of observations: 10000</span></span><br><span class="line"><span class="comment"># Number of variables: 6</span></span><br><span class="line"><span class="comment"># Variable information:</span></span><br><span class="line"><span class="comment"># Var 1: creditScore, Type: integer, Low/High: (486, 895)</span></span><br><span class="line"><span class="comment"># Var 2: houseAge, Type: integer, Low/High: (0, 40)</span></span><br><span class="line"><span class="comment"># Var 3: yearsEmploy, Type: integer, Low/High: (0, 14)</span></span><br><span class="line"><span class="comment"># Var 4: ccDebt, Type: integer, Low/High: (0, 12275)</span></span><br><span class="line"><span class="comment"># Var 5: year, Type: integer, Low/High: (2000, 2000)</span></span><br><span class="line"><span class="comment"># Var 6: default, Type: integer, Low/High: (0, 1)</span></span><br><span class="line"><span class="comment"># Data (3 rows starting with row 1):</span></span><br><span class="line"><span class="comment"># creditScore houseAge yearsEmploy ccDebt year default</span></span><br><span class="line"><span class="comment"># 1 691 16 9 6725 2000 0</span></span><br><span class="line"><span class="comment"># 2 691 4 4 5077 2000 0</span></span><br><span class="line"><span class="comment"># 3 743 18 3 3080 2000 0</span></span><br></pre></td></tr></table></figure><p><code>rxDataStep</code> 为大多数数据操作任务提供了一个框架。 它允许行选择(rowSelection参数),变量选择(varsToKeep或varsToDrop参数),以及从现有变量创建新变量(变换参数)。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">outFile2 <- <span class="literal">NULL</span></span><br><span class="line">mortDataNew <- rxDataStep(</span><br><span class="line"><span class="comment"># Specify the input data set</span></span><br><span class="line">inData = mortData,</span><br><span class="line"><span class="comment"># Put in a placeholder for an output file</span></span><br><span class="line">outFile = outFile2,</span><br><span class="line"><span class="comment"># Specify any variables to keep or drop</span></span><br><span class="line">varsToDrop = c(<span class="string">"year"</span>),</span><br><span class="line"><span class="comment"># Specify rows to select</span></span><br><span class="line">rowSelection = creditScore < <span class="number">850</span>,</span><br><span class="line"><span class="comment"># Specify a list of new variables to create</span></span><br><span class="line">transforms = list(</span><br><span class="line">catDebt = cut(ccDebt, breaks = c(<span class="number">0</span>, <span class="number">6500</span>, <span class="number">13000</span>),</span><br><span class="line">labels = c(<span class="string">"Low Debt"</span>, <span class="string">"High Debt"</span>)),</span><br><span class="line">lowScore = creditScore < <span class="number">625</span>))</span><br></pre></td></tr></table></figure><p>数据分析</p><p><code>rxSummary</code> 计算变量的表属性统计信息</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">rxSummary(~ ArrDelay, data = airXdfData)</span><br><span class="line"><span class="comment"># 输出</span></span><br><span class="line"><span class="comment">#Call:</span></span><br><span class="line"><span class="comment">#rxSummary(formula = ~ArrDelay, data = airXdfData)</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment">#Summary Statistics Results for: ~ArrDelay</span></span><br><span class="line"><span class="comment">#Data: airXdfData (RxXdfData Data Source)</span></span><br><span class="line"><span class="comment">#File name: airExample.xdf</span></span><br><span class="line"><span class="comment">#Number of valid observations: 6e+05</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment">#Name Mean StdDev Min Max ValidObs MissingObs</span></span><br><span class="line"><span class="comment">#ArrDelay 11.31794 40.68854 -86 1490 582628 17372</span></span><br></pre></td></tr></table></figure><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">rxSummary(formula = ~ArrDelay + CRSDepTime + DayOfWeek, data = airDS)</span><br><span class="line"><span class="comment"># 输出</span></span><br><span class="line"><span class="comment"># Summary Statistics Results for: ~ArrDelay + CRSDepTime + DayOfWeek</span></span><br><span class="line"><span class="comment"># Data: airXdfData (RxXdfData Data Source)</span></span><br><span class="line"><span class="comment"># File name: C:\Users\TEMP\airExample.xdf</span></span><br><span class="line"><span class="comment"># Number of valid observations: 6e+05</span></span><br><span class="line"><span class="comment"># </span></span><br><span class="line"><span class="comment"># Name Mean StdDev Min Max ValidObs MissingObs</span></span><br><span class="line"><span class="comment"># ArrDelay 11.31794 40.688536 -86.000000 1490.00000 582628 17372 </span></span><br><span class="line"><span class="comment"># CRSDepTime 13.48227 4.697566 0.016667 23.98333 600000 0 </span></span><br><span class="line"><span class="comment"># </span></span><br><span class="line"><span class="comment"># Category Counts for DayOfWeek</span></span><br><span class="line"><span class="comment"># Number of categories: 7</span></span><br><span class="line"><span class="comment"># Number of valid observations: 6e+05</span></span><br><span class="line"><span class="comment"># Number of missing observations: 0</span></span><br><span class="line"><span class="comment"># </span></span><br><span class="line"><span class="comment"># DayOfWeek Counts</span></span><br><span class="line"><span class="comment"># Monday 97975</span></span><br><span class="line"><span class="comment"># Tuesday 77725</span></span><br><span class="line"><span class="comment"># Wednesday 78875</span></span><br><span class="line"><span class="comment"># Thursday 81304</span></span><br><span class="line"><span class="comment"># Friday 82987</span></span><br><span class="line"><span class="comment"># Saturday 86159</span></span><br><span class="line"><span class="comment"># Sunday 94975</span></span><br></pre></td></tr></table></figure><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">rxSummary(~ArrDelay:DayOfWeek, data=airXdfData)</span><br><span class="line"><span class="comment"># Call:</span></span><br><span class="line"><span class="comment"># rxSummary(formula = ~ArrDelay:DayOfWeek, data=airXdfData)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Summary Statistics Results for: ~ArrDelay:DayOfWeek</span></span><br><span class="line"><span class="comment"># File name: C:\Users\TEMP\airExample.xdf</span></span><br><span class="line"><span class="comment"># Number of valid observations: 6e+05</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Name Mean StdDev Min Max ValidObs MissingObs</span></span><br><span class="line"><span class="comment"># ArrDelay:DayOfWeek 11.31794 40.68854 -86 1490 582628 17372 </span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Statistics by category (7 categories):</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Category DayOfWeek Means StdDev Min Max ValidObs</span></span><br><span class="line"><span class="comment"># ArrDelay for DayOfWeek=Monday Monday 12.025604 40.02463 -76 1017 95298 </span></span><br><span class="line"><span class="comment"># ArrDelay for DayOfWeek=Tuesday Tuesday 11.293808 43.66269 -70 1143 74011 </span></span><br><span class="line"><span class="comment"># ArrDelay for DayOfWeek=Wednesday Wednesday 10.156539 39.58803 -81 1166 76786 </span></span><br><span class="line"><span class="comment"># ArrDelay for DayOfWeek=Thursday Thursday 8.658007 36.74724 -58 1053 79145 </span></span><br><span class="line"><span class="comment"># ArrDelay for DayOfWeek=Friday Friday 14.804335 41.79260 -78 1490 80142 </span></span><br><span class="line"><span class="comment"># ArrDelay for DayOfWeek=Saturday Saturday 11.875326 45.24540 -73 1370 83851 </span></span><br><span class="line"><span class="comment"># ArrDelay for DayOfWeek=Sunday Sunday 10.331806 37.33348 -86 1202 93395</span></span><br></pre></td></tr></table></figure><p><code>rxLinMod</code> 线性回归</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">arrDelayLm2 <- rxLinMod(ArrDelay ~ DayOfWeek, data = airXdfData,</span><br><span class="line"> cube = <span class="literal">TRUE</span>)</span><br><span class="line">summary(arrDelayLm2)</span><br><span class="line"><span class="comment"># Call:</span></span><br><span class="line"><span class="comment"># rxLinMod(formula = ArrDelay ~ DayOfWeek, data = airXdfData, cube = TRUE)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Cube Linear Regression Results for: ArrDelay ~ DayOfWeek</span></span><br><span class="line"><span class="comment"># File name: C:\Users\Temp\airExample.xdf</span></span><br><span class="line"><span class="comment"># Dependent variable(s): ArrDelay</span></span><br><span class="line"><span class="comment"># Total independent variables: 7</span></span><br><span class="line"><span class="comment"># Number of valid observations: 582628</span></span><br><span class="line"><span class="comment"># Number of missing observations: 17372</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Coefficients:</span></span><br><span class="line"><span class="comment"># Estimate Std. Error t value Pr(>|t|) | Counts</span></span><br><span class="line"><span class="comment"># DayOfWeek=Monday 12.0256 0.1317 91.32 2.22e-16 *** | 95298</span></span><br><span class="line"><span class="comment"># DayOfWeek=Tuesday 11.2938 0.1494 75.58 2.22e-16 *** | 74011</span></span><br><span class="line"><span class="comment"># DayOfWeek=Wednesday 10.1565 0.1467 69.23 2.22e-16 *** | 76786</span></span><br><span class="line"><span class="comment"># DayOfWeek=Thursday 8.6580 0.1445 59.92 2.22e-16 *** | 79145</span></span><br><span class="line"><span class="comment"># DayOfWeek=Friday 14.8043 0.1436 103.10 2.22e-16 *** | 80142</span></span><br><span class="line"><span class="comment"># DayOfWeek=Saturday 11.8753 0.1404 84.59 2.22e-16 *** | 83851</span></span><br><span class="line"><span class="comment"># DayOfWeek=Sunday 10.3318 0.1330 77.67 2.22e-16 *** | 93395</span></span><br><span class="line"><span class="comment"># ---</span></span><br><span class="line"><span class="comment"># Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Residual standard error: 40.65 on 582621 degrees of freedom</span></span><br><span class="line"><span class="comment"># Multiple R-squared: 0.001869 (as if intercept included)</span></span><br><span class="line"><span class="comment"># Adjusted R-squared: 0.001858</span></span><br><span class="line"><span class="comment"># F-statistic: 181.8 on 6 and 582621 DF, p-value: < 2.2e-16</span></span><br><span class="line"><span class="comment"># Condition number: 1</span></span><br></pre></td></tr></table></figure><p><code>rxCrossTabs</code> 数据交叉表</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">myTab <- rxCrossTabs(ArrDelay~DayOfWeek, data = airLateDS)</span><br><span class="line">summary(myTab, output = <span class="string">"means"</span>)</span><br><span class="line"><span class="comment"># Call:</span></span><br><span class="line"><span class="comment"># rxCrossTabs(formula = ArrDelay ~ DayOfWeek, data = airLateDS)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Cross Tabulation Results for: ArrDelay ~ DayOfWeek</span></span><br><span class="line"><span class="comment"># File name: C:\YourWorkingDir\ADS1.xdf</span></span><br><span class="line"><span class="comment"># Dependent variable(s): ArrDelay</span></span><br><span class="line"><span class="comment"># Number of valid observations: 148526</span></span><br><span class="line"><span class="comment"># Number of missing observations: 0</span></span><br><span class="line"><span class="comment"># Statistic: means</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ArrDelay (means):</span></span><br><span class="line"><span class="comment"># means means %</span></span><br><span class="line"><span class="comment"># Monday 56.94491 13.96327</span></span><br><span class="line"><span class="comment"># Tuesday 64.28248 15.76249</span></span><br><span class="line"><span class="comment"># Wednesday 60.12724 14.74360</span></span><br><span class="line"><span class="comment"># Thursday 55.07093 13.50376</span></span><br><span class="line"><span class="comment"># Friday 56.11783 13.76047</span></span><br><span class="line"><span class="comment"># Saturday 61.92247 15.18380</span></span><br><span class="line"><span class="comment"># Sunday 53.35339 13.08261</span></span><br><span class="line"><span class="comment"># Col Mean 57.96692</span></span><br></pre></td></tr></table></figure><p><code>rxLogit</code> Logistic 回归</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">logitObj <- rxLogit(Late~DepHour + Night, data = airExtraDS)</span><br><span class="line">summary(logitObj)</span><br><span class="line"><span class="comment"># Call:</span></span><br><span class="line"><span class="comment"># rxLogit(formula = Late ~ DepHour + Night, data = airExtraDS)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Logistic Regression Results for: Late ~ DepHour + Night</span></span><br><span class="line"><span class="comment"># File name: C:\Users\Temp\ADS2.xdf</span></span><br><span class="line"><span class="comment"># Dependent variable(s): Late</span></span><br><span class="line"><span class="comment"># Total independent variables: 3</span></span><br><span class="line"><span class="comment"># Number of valid observations: 582628</span></span><br><span class="line"><span class="comment"># Number of missing observations: 17372</span></span><br><span class="line"><span class="comment"># -2*LogLikelihood: 649607.8613 (Residual deviance on 582625 degrees of freedom)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Coefficients:</span></span><br><span class="line"><span class="comment"># Estimate Std. Error z value Pr(>|z|) </span></span><br><span class="line"><span class="comment"># (Intercept) -2.0990076 0.0104460 -200.94 2.22e-16 ***</span></span><br><span class="line"><span class="comment"># DepHour 0.0790215 0.0007671 103.01 2.22e-16 ***</span></span><br><span class="line"><span class="comment"># Night -0.3027030 0.0109914 -27.54 2.22e-16 ***</span></span><br><span class="line"><span class="comment"># ---</span></span><br><span class="line"><span class="comment"># Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Condition number of final variance-covariance matrix: 3.0178</span></span><br><span class="line"><span class="comment"># Number of iterations: 4</span></span><br></pre></td></tr></table></figure><p><code>rxPredict</code> 预测</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">predictDS <- rxPredict(modelObject = logitObj, data = airExtraDS,</span><br><span class="line"> outData = airExtraDS)</span><br><span class="line">rxGetInfo(predictDS, getVarInfo=<span class="literal">TRUE</span>, numRows=<span class="number">5</span>)</span><br><span class="line"><span class="comment"># File name: C:\Users\Temp\ADS2.xdf</span></span><br><span class="line"><span class="comment"># Number of observations: 6e+05</span></span><br><span class="line"><span class="comment"># Number of variables: 7</span></span><br><span class="line"><span class="comment"># Number of blocks: 2</span></span><br><span class="line"><span class="comment"># Compression type: zlib</span></span><br><span class="line"><span class="comment"># Variable information:</span></span><br><span class="line"><span class="comment"># Var 1: ArrDelay, Type: integer, Low/High: (-86, 1490)</span></span><br><span class="line"><span class="comment"># Var 2: CRSDepTime, Type: numeric, Storage: float32, Low/High: (0.0167, 23.9833)</span></span><br><span class="line"><span class="comment"># Var 3: DayOfWeek</span></span><br><span class="line"><span class="comment"># 7 factor levels: Monday Tuesday Wednesday Thursday Friday Saturday Sunday</span></span><br><span class="line"><span class="comment"># Var 4: Late, Type: logical, Low/High: (0, 1)</span></span><br><span class="line"><span class="comment"># Var 5: DepHour, Type: integer, Low/High: (0, 23)</span></span><br><span class="line"><span class="comment"># Var 6: Night, Type: logical, Low/High: (0, 1)</span></span><br><span class="line"><span class="comment"># Var 7: Late_Pred, Type: numeric, Low/High: (0.0830, 0.3580)</span></span><br><span class="line"><span class="comment"># Data (5 rows starting with row 1):</span></span><br><span class="line"><span class="comment"># ArrDelay CRSDepTime DayOfWeek Late DepHour Night Late_Pred</span></span><br><span class="line"><span class="comment"># 1 6 9.666666 Monday FALSE 9 FALSE 0.1997569</span></span><br><span class="line"><span class="comment"># 2 -8 19.916666 Monday FALSE 19 FALSE 0.3548931</span></span><br><span class="line"><span class="comment"># 3 -2 13.750000 Monday FALSE 13 FALSE 0.2550745</span></span><br><span class="line"><span class="comment"># 4 1 11.750000 Monday FALSE 11 FALSE 0.2262214</span></span><br><span class="line"><span class="comment"># 5 -2 6.416667 Monday FALSE 6 FALSE 0.1645331</span></span><br></pre></td></tr></table></figure><h3 id="数据可视化"><a href="#数据可视化" class="headerlink" title="数据可视化"></a>数据可视化</h3><p>R 中常用的可视化函数都有对应的 rx 版本。</p><p><code>rxHistogram</code> 绘制直方图</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rxHistogram(~ArrDelay|DayOfWeek, data = airXdfData)</span><br></pre></td></tr></table></figure><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/microsoft-machine-learning-server/image2.png"><p><code>rxLinePlot</code> 绘制折线图</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">rxLinePlot(ArrDelay~DayOfWeek, data = countsDF,</span><br><span class="line"> main = <span class="string">"Average Arrival Delay by Day of Week"</span>)</span><br></pre></td></tr></table></figure><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/microsoft-machine-learning-server/dayofweek_plot.png"><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">rxLinePlot(ArrDelay~CRSDepTime|DayOfWeek, data = arrDelayDT,</span><br><span class="line"> title = <span class="string">"Average Arrival Delay by Day of Week by Departure Hour"</span>)</span><br></pre></td></tr></table></figure><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/microsoft-machine-learning-server/crsdeptime_plot.png"><h1 id="在-Machine-Learning-Server-中运行-R-代码"><a href="#在-Machine-Learning-Server-中运行-R-代码" class="headerlink" title="在 Machine Learning Server 中运行 R 代码"></a>在 Machine Learning Server 中运行 R 代码</h1><h2 id="计算上下文"><a href="#计算上下文" class="headerlink" title="计算上下文"></a>计算上下文</h2><p>在 Machine Learning Server 中,计算上下文是指处理给定工作负载的计算引擎的物理位置。 默认为本地。 但是,如果您有多台计算机,则可以从本地切换到远程,将以数据为中心的RevoScaleR(R),revoscalepy(Python),MicrosoftML(R)和microsoftml(Python)函数的执行推送到另一个系统上的计算引擎。 例如,在R Client中本地运行的脚本可以将执行转移到Spark集群中的远程机器学习服务器以在那里处理数据。</p><table><thead><tr><th>上下文</th><th>说明</th></tr></thead><tbody><tr><td>local</td><td>所有平台上的所有产品(包括R Client)均支持默认值。 脚本使用本地计算机资源在本地解释器上执行。</td></tr><tr><td>remote</td><td>专门针对选定数据平台上的机器学习服务器:Spark over Hadoop分布式文件系统(HDFS)和SQL Server。 客户端或以客户端身份运行的服务器可以启动远程计算上下文,但目标远程计算机本身必须是Machine Learning Server安装。</td></tr></tbody></table><p>RevoScaleR 的上下文包括:</p><table><thead><tr><th>上下文</th><th>别名</th><th>用法</th></tr></thead><tbody><tr><td><code>RxLocalSeq</code></td><td>local</td><td>所有服务器和客户端配置都支持本地计算上下文。</td></tr><tr><td><code>RxSpark</code></td><td>spark</td><td>远程计算上下文。 目标是Hadoop上的Spark集群。</td></tr><tr><td><code>RxInSqlServer</code></td><td>sqlserver</td><td>远程计算上下文。 目标服务器是单个数据库会话(SQL Server 2016 R服务或SQL Server 2017机器学习服务)。 计算是平行的,但不是分布式的。</td></tr><tr><td><code>RxLocalParallel</code></td><td>localpar</td><td>计算上下文通常用于依靠您提供的指令而不是Hadoop上的内置调度程序来启用受控的分布式计算。 您可以将计算上下文用于手动分布式计算。</td></tr><tr><td><code>RxForeachDoPar</code></td><td>dopar</td><td>用于手动分布式计算。</td></tr></tbody></table><p>上下文支持的数据格式</p><table><thead><tr><th style="text-align:left">Data Source</th><th style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxlocalseq" target="_blank" rel="noopener"><code>RxLocalSeq</code></a></th><th style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxspark" target="_blank" rel="noopener"><code>RxSpark</code></a></th><th style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxinsqlserver" target="_blank" rel="noopener"><code>RxInSqlServer</code></a></th></tr></thead><tbody><tr><td style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxtextdata" target="_blank" rel="noopener"><code>RxTextData</code></a></td><td style="text-align:left">X</td><td style="text-align:left">X</td><td style="text-align:left"></td></tr><tr><td style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxxdfdata" target="_blank" rel="noopener"><code>RxXdfData</code></a></td><td style="text-align:left">X</td><td style="text-align:left">X</td><td style="text-align:left"></td></tr><tr><td style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxsparkdata" target="_blank" rel="noopener"><code>RxHiveData</code></a></td><td style="text-align:left">X</td><td style="text-align:left">X</td><td style="text-align:left"></td></tr><tr><td style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxsparkdata" target="_blank" rel="noopener"><code>RxParquetData</code></a></td><td style="text-align:left">X</td><td style="text-align:left">X</td><td style="text-align:left"></td></tr><tr><td style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxsparkdata" target="_blank" rel="noopener"><code>RxOrcData</code></a></td><td style="text-align:left">X</td><td style="text-align:left">X</td><td style="text-align:left"></td></tr><tr><td style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxodbcdata" target="_blank" rel="noopener"><code>RxOdbcData</code></a></td><td style="text-align:left">X</td><td style="text-align:left"></td><td style="text-align:left"></td></tr><tr><td style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxsqlserverdata" target="_blank" rel="noopener"><code>RxSqlServerData</code></a></td><td style="text-align:left">X</td><td style="text-align:left"></td><td style="text-align:left">X</td></tr><tr><td style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxsasdata" target="_blank" rel="noopener"><code>RxSasData</code></a></td><td style="text-align:left">X</td><td style="text-align:left"></td><td style="text-align:left"></td></tr><tr><td style="text-align:left"><a href="https://docs.microsoft.com/zh-cn/machine-learning-server/r-reference/revoscaler/rxspssdata" target="_blank" rel="noopener"><code>RxSpssData</code></a></td><td style="text-align:left">X</td><td style="text-align:left"></td></tr></tbody></table><p>当数据本身发生变化时,适用于切换上下文。计算应该都是在本地进行的,只是获取数据的上下文发生了改变。</p><h2 id="远程执行-R-代码"><a href="#远程执行-R-代码" class="headerlink" title="远程执行 R 代码"></a>远程执行 R 代码</h2><p>远程执行是从机器学习服务器(或R服务器)或R客户端向另一个机器学习服务器实例上运行的远程会话发出R命令的能力。 您可以使用远程执行来卸载服务器上的繁重处理并测试您的工作。 在开发和测试分析时,它尤其有用。</p><p>远程执行支持以下几种方式:</p><ul><li>在控制台应用程序中从命令行执行</li><li>在 R 脚本中通过调用 mrsdeply 包的函数执行</li><li>通过调用 API 的代码执行</li></ul><p>远程执行可以实现的功能:</p><ul><li>登陆和登出 Machine Learning Server</li><li>生成本地和远程环境的差异报告,并协调任何差异</li><li>远程执行 R 脚本或代码</li><li>远程使用 R 对象或文件工作</li><li>创建和管理远程环境的快照以供重用</li></ul><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/microsoft-machine-learning-server/remote-execution.png"><p>远程执行的函数:</p><ul><li><code>remoteExecute</code> 用于在远程R会话中执行R代码块或R脚本的基本功能。</li><li><code>remoteScript</code> 一个简单的包装函数,用于执行远程R脚本。</li><li><code>diffLocalRemote</code> 在本地和远程之间生成“差异”报告。</li></ul><h3 id="创建远程会话"><a href="#创建远程会话" class="headerlink" title="创建远程会话"></a>创建远程会话</h3><p>使用 mrsdeploy 包的登录函数 <code>remoteLogin()</code> 或 <code>remoteLoginAAD()</code> 在 Machine Learning Server 上进行验证。设置 <code>session = TRUE</code> 创建远程会话,并设置 <code>commandline = TRUE</code> 进入远程控制台。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">remoteLogin(<span class="string">"http://localhost:12800"</span>, </span><br><span class="line"> username = <span class="string">"admin"</span>, </span><br><span class="line"> password = <span class="string">"{{YOUR_PASSWORD}}"</span>,</span><br><span class="line"> diff = <span class="literal">TRUE</span>,</span><br><span class="line"> session = <span class="literal">TRUE</span>,</span><br><span class="line"> commandline = <span class="literal">TRUE</span>)</span><br></pre></td></tr></table></figure><p>参数说明:</p><table><thead><tr><th>参数</th><th>描述</th></tr></thead><tbody><tr><td><code>endpoint</code></td><td>Machine Learning Server HTTP / HTTPS端点,包括端口号。 启动管理实用程序时,可以在第一个屏幕上找到此项。</td></tr><tr><td><code>session</code></td><td>如果为 <code>TRUE</code> ,则创建远程会话。 如果省略,则创建远程会话。</td></tr><tr><td><code>diff</code></td><td>如果为 <code>TRUE</code> ,则创建一个“差异”报告,显示本地会话和远程会话之间的差异。 参数仅在会话参数为TRUE时有效。</td></tr><tr><td><code>commandline</code></td><td>如果为 <code>TRUE</code> ,则在R控制台中创建“REMOTE”命令行。参数仅在会话参数为 <code>TRUE</code> 时有效。如果省略,则与 <code>= TRUE</code> 相同。</td></tr><tr><td><code>prompt</code></td><td>用于远程会话的命令提示符。 默认情况下,使用 <code>REMOTE></code> 。</td></tr><tr><td><code>username</code></td><td>如果为 <code>NULL</code> ,则提示用户输入您的AD或本地计算机学习服务器用户名。</td></tr><tr><td><code>password</code></td><td>如果为 <code>NULL</code> ,则提示用户输入密码。</td></tr></tbody></table><blockquote><p><code>remoteLoginAAD()</code> 函数用于 Azure 云的登录。</p></blockquote><h3 id="在会话间切换或退出"><a href="#在会话间切换或退出" class="headerlink" title="在会话间切换或退出"></a>在会话间切换或退出</h3><p>使用三个函数进行远程会话和本地会话切换,以及退出:</p><ul><li><code>pause()</code> 从远程会话切换到本地会话</li><li><code>resume()</code> 从本地会话切换到远程会话</li><li><code>remoteLogout()</code> 登出 Machine Learning Server</li></ul><p>使用参数 <code>session = TRUE</code> 登录到远程R服务器后,将创建一个远程 R 会话。 您可以直接从命令行在远程 R 会话和本地 R 会话之间切换。 远程命令行允许您直接与另一台计算机上的 R Server 9.x 实例进行交互。</p><p>当R控制台中显示 <code>REMOTE></code> 命令提示符时,输入的任何R命令都在远程R会话上执行。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/microsoft-machine-learning-server/mrsdeploy-connect-switch-context.png"><p>使用以下函数在本地命令行和远程命令行之间切换:<code>pause()</code> 和 <code>resume()</code>。 要切换回本地 R 会话,请键入“<code>pause()</code>”。 如果已切换到本地R会话,则可以通过键入“ <code>resume()</code>”返回远程 R 会话。</p><p>要终止远程R会话,请在REMOTE>提示符下键入“<code>exit</code>”。 此外,要从本地R会话终止远程会话,请键入“<code>remoteLogout()</code>”。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#EXAMPLE: SESSION SWITCHING </span></span><br><span class="line"></span><br><span class="line"><span class="comment">#execute some R commands on the remote session</span></span><br><span class="line">REMOTE>x<-rnorm(<span class="number">1000</span>)</span><br><span class="line">REMOTE>hist(x)</span><br><span class="line"></span><br><span class="line">REMOTE>pause() <span class="comment">#switches the user to the local R session</span></span><br><span class="line">>resume() </span><br><span class="line"></span><br><span class="line">REMOTE>exit <span class="comment">#logout and terminate the remote R session</span></span><br><span class="line">></span><br></pre></td></tr></table></figure><h3 id="远程执行-R-脚本"><a href="#远程执行-R-脚本" class="headerlink" title="远程执行 R 脚本"></a>远程执行 R 脚本</h3><p>如果本地计算机上有R脚本,则可以使用 <code>remoteScript()</code>函数远程执行它们。 此函数采用远程执行R脚本的路径。 您还可以选择保存或显示脚本执行期间可能生成的任何图。 该函数返回一个列表,其中包含执行状态(成功/失败),生成的控制台输出以及创建的文件列表。</p><p>如果您的R脚本具有R包依赖项,则必须在 Microsoft R Server 上安装这些包。 您的管理员可以在服务器上全局安装它们,也可以使用 <code>install.packages()</code> 函数在远程会话期间自行安装它们。 将 <code>lib</code> 参数留空。</p><p>远程上下文的限制:</p><ul><li>某些功能在执行时被屏蔽,例如“<code>help</code>”,“<code>browser</code>”,“<code>q</code>”和“<code>quit</code>”。</li><li>在远程上下文中,您无法在命令行提示符下显示晕影或获取帮助。</li><li>在大多数情况下,“<code>system</code>”命令有效。 但是,写入stdout/stderr的系统命令可能不会显示其输出,也不会等到整个系统命令完成后才显示输出。 <code>install.packages</code> 是我们在远程上下文中显式处理 stdout 和 stderr 的唯一例外。</li></ul><p>要在远程脚本执行期间继续在开发环境中工作,可以异步执行 R 脚本。 当您运行具有较长执行时间的脚本时,异步脚本执行非常有用。要异步执行 R 脚本,请将 <code>remoteScript()</code> 的 <code>async</code> 参数设置为 <code>TRUE</code> 。 执行 <code>remoteScript()</code> 时,脚本将在新的远程 R 控制台窗口中异步运行。 所有 R 控制台输出和来自该执行的任何图都返回到同一窗口。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#EXAMPLE: REMOTE SCRIPT EXECUTION </span></span><br><span class="line"></span><br><span class="line"><span class="comment">#install a package for the life of the session</span></span><br><span class="line">REMOTE>install.packages(<span class="string">"bitops"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">#switch to the local R session</span></span><br><span class="line">REMOTE>pause()</span><br><span class="line"></span><br><span class="line"><span class="comment">#execute an R script remotely</span></span><br><span class="line">>remoteScript(<span class="string">"C:/myScript.R"</span>) </span><br><span class="line"></span><br><span class="line"><span class="comment">#execute that script again in another window asynchronously</span></span><br><span class="line">>remoteScript(<span class="string">"C:/myScript.R"</span>, async=<span class="literal">TRUE</span>)</span><br></pre></td></tr></table></figure><h3 id="远程使用R对象和文件"><a href="#远程使用R对象和文件" class="headerlink" title="远程使用R对象和文件"></a>远程使用R对象和文件</h3><p>远程执行R代码后,您可能希望检索某些R对象并将其加载到本地R会话中。 例如,如果您有一个创建线性模型 <code>m <-lm(x~y)</code> 的R脚本,请使用函数 <code>getRemoteObject()</code> 来检索本地R会话中的对象 <code>m</code> 。</p><p>相反,如果您希望远程 R 会话可以使用本地 R 对象,则可以使用函数 <code>putLocalObject()</code> 。 如果要同步本地和远程工作空间,可以使用 <code>putLocalWorkspace()</code> 和 <code>getRemoteWorkspace()</code> 函数。</p><p>类似的功能可用于需要在本地和远程 R 会话之间移动的文件。以下函数可用于处理文件: <code>putLocalFile()</code> 、 <code>getRemoteFile()</code> 、 <code>listRemoteFiles()</code> 和 <code>deleteRemoteFile()</code> 。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#EXAMPLE: REMOTE R OBJECTS AND FILES </span></span><br><span class="line"></span><br><span class="line"><span class="comment">#execute a script remotely that generated 2 R objects we are interested in retrieving</span></span><br><span class="line">>remoteExecute(<span class="string">"C:/myScript.R"</span>)</span><br><span class="line"><span class="comment">#retrieve the R objects from the remote R session and load them into our local R session</span></span><br><span class="line">>getRemoteObject(c(<span class="string">"model"</span>,<span class="string">"out"</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">#an R script depends upon an R object named `data` to be available. Move the local</span></span><br><span class="line"><span class="comment">#instance of `data` to the remote R session</span></span><br><span class="line">>putLocalObject(<span class="string">"data"</span>)</span><br><span class="line"><span class="comment">#execute an R script remotely</span></span><br><span class="line">>remoteScript(<span class="string">"C:/myScript2.R"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">#push a data file to the remote R session</span></span><br><span class="line">>putLocalFile(<span class="string">"C:/data/survey.csv"</span>)</span><br><span class="line"><span class="comment">#execute an R script remotely</span></span><br><span class="line">>remoteScript(<span class="string">"C:/myScript2.R"</span>)</span><br></pre></td></tr></table></figure><h3 id="绘图的注意事项"><a href="#绘图的注意事项" class="headerlink" title="绘图的注意事项"></a>绘图的注意事项</h3><p>远程绘制时,默认绘图大小为400 x 400像素。 如果您需要更高分辨率的输出,则必须告诉远程会话要创建的绘图大小。</p><p>在本地会话中,您可以更改宽度和高度,如下所示:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">> png(filename=<span class="string">"myplot.png"</span>, width=<span class="number">1440</span>, height=<span class="number">900</span>)</span><br><span class="line">> ggplot(aes(x=value, group=am, colour=factor(am)), data=mtcarsmelt) + geom_density() + facet_wrap(~variable, scales=<span class="string">"free"</span>)</span><br><span class="line">> dev.off()</span><br></pre></td></tr></table></figure><p>在处理REMOTE命令行时,您需要将这三个语句组合在一起:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">REMOTE> png(filename=<span class="string">"myplot.png"</span>, width=<span class="number">1440</span>, height=<span class="number">900</span>);ggplot(aes(x=value, group=am, colour=factor(am)), data=mtcarsmelt) + geom_density() + facet_wrap(~variable, scales=<span class="string">"free"</span>);dev.off()</span><br></pre></td></tr></table></figure><p>作为替代方法,您可以使用 <code>remoteScript()</code> 函数,如下所示:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#Open a new script window in your IDE</span></span><br><span class="line"><span class="comment">#Enter the commands on separate lines</span></span><br><span class="line"></span><br><span class="line">png(filename=<span class="string">"myplot.png"</span>, width=<span class="number">1440</span>, height=<span class="number">900</span>)</span><br><span class="line">ggplot(aes(x=value, group=am, colour=factor(am)), data=mtcarsmelt) + geom_density() + facet_wrap(~variable, scales=<span class="string">"free"</span>)</span><br><span class="line">dev.off()</span><br></pre></td></tr></table></figure><h1 id="将模型作为-Web-服务发布"><a href="#将模型作为-Web-服务发布" class="headerlink" title="将模型作为 Web 服务发布"></a>将模型作为 Web 服务发布</h1><p><code>mrsdeploy</code> 库提供了一些函数,使 R 模型可以作为 Web 服务发布。</p><h2 id="主要流程"><a href="#主要流程" class="headerlink" title="主要流程"></a>主要流程</h2><ol><li>本地编写模型</li><li>将模型作为 Web 服务发布</li><li>在 R 中使用服务进行测试</li><li>获取基于 Swagger 的 JSON 文件</li><li>基于 Swagger 文件与 Web 服务集成</li></ol><blockquote><p><strong><em>Swagger</em></strong></p><p>大部分 Web 应用程序都支持 RESTful API,但不同于 SOAP API——REST API 依赖于 HTTP 方法,缺少与 Web 服务描述语言(Web Services Description Language,WSDL)类似的语言来定义使用者与提供者之间的请求和响应结构。由于没有充分的合同服务,许多 REST API 提供者使用 Microsoft Word 文档或维基页面来记录 API 用法。这些格式使协作和文档版本控制变得很困难,尤其对于有许多 API 或资源的应用程序,或者在 API 采用迭代式开发方式时。这些文档类型在集成到自动化测试应用程序中时变得更难。</p><p>开源 <a href="http://swagger.io/" target="_blank" rel="noopener">Swagger</a> 框架帮助 API 使用者和开发人员纠正了这些问题。该框架为创建 JSON 或 <a href="http://www.yaml.org/" target="_blank" rel="noopener">YAML</a>(JSON 的一个人性化的超集)格式的 RESTful API 文档提供了 <a href="http://swagger.io/specification/" target="_blank" rel="noopener">OpenAPI 规范</a>(以前称为 Swagger 规范)。Swagger 文档可由各种编程语言处理,可在软件开发周期中签入源代码控制系统中,以便进行版本管理。</p></blockquote><h2 id="发布服务"><a href="#发布服务" class="headerlink" title="发布服务"></a>发布服务</h2><p>服务主要包括<strong>模型</strong>、<strong>代码</strong>两部分,模型即指一些模型对象,如 <code>glm</code> 函数的结果;代码是指调用模型的过程,主要对传输参数进行一些处理。</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># For R Server 9.0, load mrsdeploy package on R Server </span></span><br><span class="line"><span class="keyword">library</span>(mrsdeploy)</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- AAD login ----------------------------------------------------------------</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Use `remoteLogin` to authenticate with R Server using </span></span><br><span class="line"><span class="comment"># the local admin account. Use session = false so no </span></span><br><span class="line"><span class="comment"># remote R session started</span></span><br><span class="line"><span class="comment"># REMEMBER: Replace with your login details</span></span><br><span class="line">remoteLogin(<span class="string">"http://localhost:12800"</span>, </span><br><span class="line"> username = “admin”, </span><br><span class="line"> password = “{{YOUR_PASSWORD}}”,</span><br><span class="line"> session = <span class="literal">FALSE</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Information can come from a file</span></span><br><span class="line">model <- <span class="string">"model <- glm(formula = am ~ hp + wt, data = mtcars, family = binomial)"</span></span><br><span class="line">code <- <span class="string">"newdata <- data.frame(hp = hp, wt = wt)\n</span></span><br><span class="line"><span class="string"> answer <- predict(model, newdata, type = 'response')"</span></span><br><span class="line"></span><br><span class="line">cat(model, file = <span class="string">"transmission.R"</span>, append = <span class="literal">FALSE</span>)</span><br><span class="line">cat(code, file = <span class="string">"transmission-code.R"</span>, append = <span class="literal">FALSE</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Generate a unique serviceName for demos </span></span><br><span class="line"><span class="comment"># and assign to variable serviceName</span></span><br><span class="line">serviceName <- paste0(<span class="string">"mtService"</span>, round(as.numeric(Sys.time()), <span class="number">0</span>))</span><br><span class="line"></span><br><span class="line">api <- publishService(</span><br><span class="line"> serviceName,</span><br><span class="line"> code = <span class="string">"transmission-code.R"</span>,</span><br><span class="line"> model = <span class="string">"transmission.R"</span>,</span><br><span class="line"> inputs = list(hp = <span class="string">"numeric"</span>, wt = <span class="string">"numeric"</span>),</span><br><span class="line"> outputs = list(answer = <span class="string">"numeric"</span>),</span><br><span class="line"> v = <span class="string">"v1.0.3"</span>,</span><br><span class="line"> alias = <span class="string">"manualTransmission"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">api</span><br><span class="line"></span><br><span class="line">result <- api$manualTransmission(<span class="number">120</span>, <span class="number">2.8</span>)</span><br><span class="line">result</span><br><span class="line">print(result$output(<span class="string">"answer"</span>)) <span class="comment"># 0.6418125</span></span><br><span class="line"></span><br><span class="line">swagger <- api$swagger()</span><br><span class="line">cat(swagger)</span><br><span class="line"></span><br><span class="line">swagger <- api$swagger(json = <span class="literal">FALSE</span>)</span><br><span class="line">swagger</span><br><span class="line"></span><br><span class="line">services <- listServices(serviceName)</span><br><span class="line">services</span><br><span class="line"></span><br><span class="line">serviceName <- services[[<span class="number">1</span>]]</span><br><span class="line">serviceName</span><br><span class="line"></span><br><span class="line">api <- getService(serviceName$name, serviceName$version)</span><br><span class="line">api</span><br><span class="line">result <- api$manualTransmission(<span class="number">120</span>, <span class="number">2.8</span>)</span><br><span class="line">print(result$output(<span class="string">"answer"</span>)) <span class="comment"># 0.6418125</span></span><br><span class="line"></span><br><span class="line">cap <- api$capabilities()</span><br><span class="line">cap</span><br><span class="line">cap$swagger</span><br><span class="line"></span><br><span class="line">status <- deleteService(cap$name, cap$version)</span><br><span class="line">status</span><br><span class="line"></span><br><span class="line">remoteLogout()</span><br></pre></td></tr></table></figure><p>服务支持的参数的类型和返回值类型均为以下几种:</p><ul><li>numeric</li><li>integer</li><li>logical</li><li>character</li><li>vector</li><li>matrix</li><li>data.frame</li></ul><p>如果 <code>code</code> 是函数,只有一个值可以返回。</p><h2 id="代码和模型"><a href="#代码和模型" class="headerlink" title="代码和模型"></a>代码和模型</h2><h3 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h3><p>官方文档没有明确指出 Code 和 Model 的区别。但是从代码和性能分析的角度,可以发现 Code 和 Model 有以下区别:</p><ol><li>Model 部分的代码是发布服务的时候执行的, Code 部分则是在接收网络请求的时候执行的。因为 Model 部分只包含一个模型,不可能在每次网络请求中都执行,否则求解模型的时间会很长。在网络请求中, Model 部分会被加载到 Code 部分。</li><li>Model 部分不接收网络请求的参数(即发布服务时指定的 <code>inputs</code> ),也不提供返回参数(即发布服务时指定的 <code>outputs</code> )。接收参数和返回参数的提供是在 Code 部分中进行的。</li><li>Model 部分一般是 R 对象, Code 部分一般是可执行体。</li></ol><p>由于有这样的区别,因此使用动态模型只能在 Code 中进行,而且最好将接口设计为异步接口。</p><h3 id="类型"><a href="#类型" class="headerlink" title="类型"></a>类型</h3><table><thead><tr><th></th><th>来源</th></tr></thead><tbody><tr><td>Code</td><td>指向 R 脚本的文件路径</td></tr><tr><td></td><td>字符串形式的 R 代码片段</td></tr><tr><td></td><td>R 函数</td></tr><tr><td>Model</td><td>指向 .RData 文件的路径</td></tr><tr><td></td><td>指向 R 脚本的文件路径,用于加载环境</td></tr><tr><td></td><td>模型对象,如 <code>model = am.glm</code></td></tr></tbody></table><h2 id="与-Web-服务集成"><a href="#与-Web-服务集成" class="headerlink" title="与 Web 服务集成"></a>与 Web 服务集成</h2><p>发布的 R 服务可以与 Web 服务集成。利用服务提供的 swagger.json 文件,可以使用 Swagger 自动生成 API 接口代码以及文档。主要流程为:</p><ol><li>获取 Swagger 工具和文件</li><li>使用 Swagger 生成 API 库</li><li>添加认证逻辑</li><li>通过库与 API 交互或使用服务</li></ol><p>以 mtService 与 ASP.NET 为例,首先创建 ASP.NET Web API 工程。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/microsoft-machine-learning-server/1556151659023.png"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/microsoft-machine-learning-server/1556152239472.png"><blockquote><p><strong><em>ASP.NET MVC 框架</em></strong></p><p>MVC 是三个 ASP.NET 开发模型之一。</p><p>MVC 是用于构建 web 应用程序的一种框架,使用 MVC (Model View Controller) 设计:</p><ul><li>Model(模型)表示应用程序核心(比如数据库记录列表)。是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象在数据库中存取数据。</li><li>View(视图)对数据(数据库记录)进行显示。是应用程序中处理数据显示的部分。通常从模型数据中创建视图。</li><li>Controller(控制器)处理输入(写入数据库记录)。是应用程序中处理用户交互的部分。通常控制器从视图读取数据、控制用户输入,并向模型发送数据数据。</li></ul><p>MVC 的这种拆分有助于我们管理复杂的应用程序,因为您能够在同一时间关注一个方面。例如,您可以在不依赖业务逻辑的情况下对视图进行设计。同时对应用程序的设计也更加容易。MVC 的这种拆分同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。</p></blockquote><p>然后使用 AutoRest 或 Swagger Codegen 生成 C# 代码:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">AutoRest.exe -CodeGenerator CSharp -Modeler Swagger -Input swagger.json -Namespace Transmission</span><br></pre></td></tr></table></figure><p>会在 swagger.json 所在文件夹下创建一个 Generate 文件夹,里面保存了生成的代码,文件结构如下:</p><ul><li>Models<ul><li>AccessTokenResponse.cs</li><li>BatchWebServiceResult.cs</li><li>Error.cs</li><li>ErrorException.cs</li><li>InputParameters.cs</li><li>LoginRequest.cs</li><li>OutputParameters.cs</li><li>RenewTokenRequest.cs</li><li>RenewTokenRequest.cs</li><li>StartBatchExecutionResponse.cs</li><li>WebServiceResult.cs</li></ul></li><li>IMtService1556106844.cs</li><li>MtService1556106844.cs</li><li>MtService1556106844Extensions.cs</li></ul><p>每个文件都包含一个类,这些类都声明在 <code>Transmission</code> 命名空间中,结合 C# 命名空间取值的特点,建议将这些文件放在 ASP.NET 工程根目录的 Transmission 目录下。</p><blockquote><p>另外一种方式,是将命名空间声明为 RService.Transmission ,可以将 R 服务都放置在工程根目录的 RService 目录下(如将这个服务放置在工程根目录的 RService/Transmission 目录下),便于多个接口的管理。</p></blockquote><p>然后 Controllers 文件夹下,创建一个 Web API 控制器类,命名为 <code>MtServiceController</code> 。即可生成一个 Controller 的基本代码。</p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/microsoft-machine-learning-server/1556152306950.png"><p>基本代码如下:</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> System;</span><br><span class="line"><span class="keyword">using</span> System.Collections.Generic;</span><br><span class="line"><span class="keyword">using</span> System.Linq;</span><br><span class="line"><span class="keyword">using</span> System.Net;</span><br><span class="line"><span class="keyword">using</span> System.Net.Http;</span><br><span class="line"><span class="keyword">using</span> System.Web.Http;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">TestRService.Controllers</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">class</span> <span class="title">TempController</span> : <span class="title">ApiController</span></span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// GET api/<controller></span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> IEnumerable<<span class="keyword">string</span>> <span class="title">Get</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="keyword">string</span>[] { <span class="string">"value1"</span>, <span class="string">"value2"</span> };</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// GET api/<controller>/5</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">string</span> <span class="title">Get</span>(<span class="params"><span class="keyword">int</span> id</span>)</span></span><br><span class="line"><span class="function"></span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"value"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// POST api/<controller></span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Post</span>(<span class="params">[FromBody]<span class="keyword">string</span> <span class="keyword">value</span></span>)</span></span><br><span class="line"><span class="function"></span> {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// PUT api/<controller>/5</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Put</span>(<span class="params"><span class="keyword">int</span> id, [FromBody]<span class="keyword">string</span> <span class="keyword">value</span></span>)</span></span><br><span class="line"><span class="function"></span> {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// DELETE api/<controller>/5</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Delete</span>(<span class="params"><span class="keyword">int</span> id</span>)</span></span><br><span class="line"><span class="function"></span> {</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>该基本代码提供了 GET 、 POST 、 PUT 、 DELETE 四种 HTTP 请求类型的支持。现在只需要对这些进行修改。即可。</p><p>对带参数的 GET 方法进行修改的方法</p><ol><li><p>将参数修改为两个: <code>hp</code> 和 <code>wt</code> 。直接修改 <code>public string Get(int id)</code> 的声明:</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="deletion">- public string Get(int id)</span></span><br><span class="line"><span class="addition">+ public double? Get(double hp, double wt)</span></span><br></pre></td></tr></table></figure><p>即对 GET 方法添加了两个参数,都是 <code>double</code> 类型。</p></li><li><p>创建 R 服务对象,指明服务基地址。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">double</span>? Get(<span class="keyword">double</span> hp, <span class="keyword">double</span> wt)</span><br><span class="line">{</span><br><span class="line"> MtService1556106844 client = <span class="keyword">new</span> MtService1556106844(<span class="keyword">new</span> Uri(<span class="string">"http://192.168.41.49:12800"</span>));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在有 SSL 证书时可以使用 HTTPS 协议。</p></li><li><p>添加服务的认证:在 Get 方法中使用 Transmission 库自带的 <code>LoginRequest</code> 类进行认证。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">double</span>? Get(<span class="keyword">double</span> hp, <span class="keyword">double</span> wt)</span><br><span class="line">{</span><br><span class="line"> MtService1556106844 client = <span class="keyword">new</span> MtService1556106844(<span class="keyword">new</span> Uri(<span class="string">"http://192.168.41.49:12800"</span>));</span><br><span class="line"> <span class="comment">// --- AUTHENTICATE WITH ACTIVE DIRECTORY -----------------------------------------</span></span><br><span class="line"> <span class="comment">// Note - Update these with your appropriate values</span></span><br><span class="line"> <span class="comment">// Once authenticated, user won't provide credentials again until token is invalid.</span></span><br><span class="line"> <span class="comment">// You can now begin to interact with the operationalization APIs</span></span><br><span class="line"> <span class="comment">// --------------------------------------------------------------------------------</span></span><br><span class="line"> <span class="keyword">var</span> loginRequest = <span class="keyword">new</span> LoginRequest(<span class="string">"admin"</span>, <span class="string">"GWmodel-Lab2018"</span>);</span><br><span class="line"> <span class="keyword">var</span> loginResponse = client.Login(loginRequest);</span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="comment">// SET AUTHORIZATION HEADER WITH BEARER ACCESS TOKEN FOR FUTURE CALLS</span></span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="keyword">var</span> headers = client.HttpClient.DefaultRequestHeaders;</span><br><span class="line"> <span class="keyword">var</span> accessToken = loginResponse.AccessToken;</span><br><span class="line"> headers.Remove(<span class="string">"Authorization"</span>);</span><br><span class="line"> headers.Add(<span class="string">"Authorization"</span>, <span class="string">$"Bearer <span class="subst">{accessToken}</span>"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的用户名 <code>admin</code> 和密码 <code>GWmodel-Lab2018</code> 是 Machine Learning Server 在启动的时候,设置的用户名和密码。</p></li><li><p>调用 R 服务并返回结果:在 Get 方法中使用 Transmission 库调用 R 服务。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">double</span>? Get(<span class="keyword">double</span> hp, <span class="keyword">double</span> wt)</span><br><span class="line">{</span><br><span class="line"> MtService1556106844 client = <span class="keyword">new</span> MtService1556106844(<span class="keyword">new</span> Uri(<span class="string">"http://192.168.41.49:12800"</span>));</span><br><span class="line"> <span class="comment">// --- AUTHENTICATE WITH ACTIVE DIRECTORY -----------------------------------------</span></span><br><span class="line"> <span class="comment">// Note - Update these with your appropriate values</span></span><br><span class="line"> <span class="comment">// Once authenticated, user won't provide credentials again until token is invalid.</span></span><br><span class="line"> <span class="comment">// You can now begin to interact with the operationalization APIs</span></span><br><span class="line"> <span class="comment">// --------------------------------------------------------------------------------</span></span><br><span class="line"> <span class="keyword">var</span> loginRequest = <span class="keyword">new</span> LoginRequest(<span class="string">"admin"</span>, <span class="string">"GWmodel-Lab2018"</span>);</span><br><span class="line"> <span class="keyword">var</span> loginResponse = client.Login(loginRequest);</span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="comment">// SET AUTHORIZATION HEADER WITH BEARER ACCESS TOKEN FOR FUTURE CALLS</span></span><br><span class="line"> <span class="comment">//</span></span><br><span class="line"> <span class="keyword">var</span> headers = client.HttpClient.DefaultRequestHeaders;</span><br><span class="line"> <span class="keyword">var</span> accessToken = loginResponse.AccessToken;</span><br><span class="line"> headers.Remove(<span class="string">"Authorization"</span>);</span><br><span class="line"> headers.Add(<span class="string">"Authorization"</span>, <span class="string">$"Bearer <span class="subst">{accessToken}</span>"</span>);</span><br><span class="line"> </span><br><span class="line"> InputParameters inputs = <span class="keyword">new</span> InputParameters() { Hp = hp, Wt = wt };</span><br><span class="line"><span class="keyword">var</span> serviceResult = client.ManualTransmission(inputs);</span><br><span class="line"><span class="keyword">return</span> serviceResult.OutputParameters.Answer;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><p>POST 方法、 PUT 方法的编写本质上和 GET 方法的编写没有什么区别。不同的地方在于, POST 方法中的参数通过请求体进行,而不是请求地址。参数类型可以直接声明为 <code>InputParameters</code> 类型,因为这个类型支持 JSON 反序列化。</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> <span class="title">Transmission.Models</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">using</span> Newtonsoft.Json;</span><br><span class="line"> <span class="keyword">using</span> System.Linq;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">partial</span> <span class="keyword">class</span> <span class="title">InputParameters</span></span><br><span class="line"> {</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">InputParameters</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span> {</span><br><span class="line"> CustomInit();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">InputParameters</span>(<span class="params"><span class="keyword">double</span>? hp = <span class="keyword">default</span>(<span class="keyword">double</span>?</span>), <span class="keyword">double</span>? wt</span> = <span class="keyword">default</span>(<span class="keyword">double</span>?))</span><br><span class="line"> {</span><br><span class="line"> Hp = hp;</span><br><span class="line"> Wt = wt;</span><br><span class="line"> CustomInit();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">partial</span> <span class="keyword">void</span> <span class="title">CustomInit</span>(<span class="params"></span>)</span>;</span><br><span class="line"></span><br><span class="line"> [<span class="meta">JsonProperty(PropertyName = <span class="meta-string">"hp"</span>)</span>]</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">double</span>? Hp { <span class="keyword">get</span>; <span class="keyword">set</span>; }</span><br><span class="line"></span><br><span class="line"> [<span class="meta">JsonProperty(PropertyName = <span class="meta-string">"wt"</span>)</span>]</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">double</span>? Wt { <span class="keyword">get</span>; <span class="keyword">set</span>; }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当请求体是如下的 HTTP 请求时,程序可以自动获取到这些参数:</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">POST</span> <span class="string">http://localhost:64620/api/MtService</span> HTTP/1.1</span><br><span class="line"><span class="attribute">Content-Type</span>: application/json</span><br><span class="line"></span><br><span class="line">{</span><br><span class="line"> "hp": 120,</span><br><span class="line"> "wt": 2.8</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>另外,在 GET 等方法的返回值中,如果 R 服务的返回值类型是 vector 、 matrix 或 data.frame ,那么不能直接以 XML 返回,需要调用 <code>ToString()</code> 函数返回。但是可以直接以 JSON 返回。方法是:</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">public double? Get(double hp, double wt)</span><br><span class="line">{</span><br><span class="line"> MtService1556106844 client = new MtService1556106844(new Uri("http://192.168.41.49:12800"));</span><br><span class="line"> // --- AUTHENTICATE WITH ACTIVE DIRECTORY -----------------------------------------</span><br><span class="line"> // Note - Update these with your appropriate values</span><br><span class="line"> // Once authenticated, user won't provide credentials again until token is invalid.</span><br><span class="line"> // You can now begin to interact with the operationalization APIs</span><br><span class="line"> // --------------------------------------------------------------------------------</span><br><span class="line"> var loginRequest = new LoginRequest("admin", "GWmodel-Lab2018");</span><br><span class="line"> var loginResponse = client.Login(loginRequest);</span><br><span class="line"> //</span><br><span class="line"> // SET AUTHORIZATION HEADER WITH BEARER ACCESS TOKEN FOR FUTURE CALLS</span><br><span class="line"> //</span><br><span class="line"> var headers = client.HttpClient.DefaultRequestHeaders;</span><br><span class="line"> var accessToken = loginResponse.AccessToken;</span><br><span class="line"> headers.Remove("Authorization");</span><br><span class="line"> headers.Add("Authorization", $"Bearer {accessToken}");</span><br><span class="line"> </span><br><span class="line"> InputParameters inputs = new InputParameters() { Hp = hp, Wt = wt };</span><br><span class="line">var serviceResult = client.ManualTransmission(inputs);</span><br><span class="line"><span class="deletion">-return serviceResult.OutputParameters.Answer;</span></span><br><span class="line"><span class="addition">+return Json(serviceResult.OutputParameters.Answer);</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="连接池"><a href="#连接池" class="headerlink" title="连接池"></a>连接池</h2><p>当您提前创建会话和加载依赖项时,可以快速连接到Web服务。 会话在专用于特定Web服务的池中可用,其中每个会话包括R解释器的实例和Web服务所需的依赖关系的副本。 例如,如果您为使用Matplotlib,dplyr,cluster,RevoScaleR,MicrosoftML和mrsdeploy的Web服务提前创建了10个会话,则每个会话都将拥有自己的R解释器实例以及内存中加载的每个库的副本。</p><p>具有专用会话池的Web服务从不请求来自通用会话池共享资源的连接,即使在达到最大会话时也是如此。 通用会话池仅为那些没有专用资源的Web服务提供服务。</p><p><code>mrsdeploy</code> 提供以下三个函数来创建和管理会话:</p><ul><li>configureServicePool</li><li>getPoolStatus</li><li>deleteServicePool</li></ul><p>创建连接池:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># load mrsdeploy and print the function list</span></span><br><span class="line"><span class="keyword">library</span>(mrsdeploy)</span><br><span class="line">ls(<span class="string">"package:mrsdeploy"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Return a list of web services to get the service and version </span></span><br><span class="line"><span class="comment"># Both service name and version number are required</span></span><br><span class="line">listServices()</span><br><span class="line"></span><br><span class="line"><span class="comment"># Create the session pool using a case-sensitive web service name</span></span><br><span class="line"><span class="comment"># A status code of 200 is returned upon success</span></span><br><span class="line">configureServicePool(name = <span class="string">"myWebservice1234"</span>, version = <span class="string">"v1.0.0"</span>, initialPoolSize = <span class="number">5</span>, maxPoolSize = <span class="number">10</span> )</span><br><span class="line"></span><br><span class="line"><span class="comment"># Return status </span></span><br><span class="line"><span class="comment"># Pending indicates session creation is in progress. Success indicates sessions are ready.</span></span><br><span class="line">getPoolStatus(name = <span class="string">"myWebService1234"</span>, version = <span class="string">"v1.0.0"</span>)</span><br></pre></td></tr></table></figure><p>删除连接池:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Return a list of web services to get the service and version information</span></span><br><span class="line">listServices()</span><br><span class="line"></span><br><span class="line"><span class="comment"># Deletes the dedicated session pool and releases resources</span></span><br><span class="line">deleteServicePool(name = <span class="string">"myWebService1234"</span>, version = <span class="string">"v1.0.0"</span>)</span><br></pre></td></tr></table></figure><p>获取连接池信息:</p><figure class="highlight r"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Deletes the dedicated session pool and releases resources</span></span><br><span class="line">deleteServicePool(name = <span class="string">"myWebService1234"</span>, version = <span class="string">"v1.0.0"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Check the real-time status of dedicated pool</span></span><br><span class="line">getPoolStatus(name = <span class="string">"myWebService1234"</span>, version = <span class="string">"v1.0.0"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># make sure the return status is NotFound on all computeNodes</span></span><br><span class="line"><span class="comment"># if not, issue anthor deleteServicePool command again</span></span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h1 id="Machine-Learning-Server-主要功能"><a href="#Machine-Learning-Server-主要功能" class="headerlink" title="Machine Learning Server 主要功能"></a>Ma
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="R" scheme="http://hpdell.github.io/tags/R/"/>
</entry>
<entry>
<title>通过 Metaweblog API 给 Hexo 接入客户端</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/hexo-metaweblog-guide/"/>
<id>http://hpdell.github.io/编程/hexo-metaweblog-guide/</id>
<published>2019-02-26T16:09:28.000Z</published>
<updated>2022-04-14T16:50:55.449Z</updated>
<content type="html"><![CDATA[<h1 id="Hexo-搭建静态博客的问题"><a href="#Hexo-搭建静态博客的问题" class="headerlink" title="Hexo 搭建静态博客的问题"></a>Hexo 搭建静态博客的问题</h1><p>Hexo + GitHub Pages 搭建博客的方式已经非常流行了。但是 Hexo 的写作几乎必须在电脑上进行。在 Travis 等 CI 工具的支持下,可以通过在 GitHub 页面上新建文件,编写内容,然后通过 Travis CI 持续集成,发布到 GitHub Pages 。但这个流程相当麻烦,而且在 iOS 上 GitHub 网页的那个编辑器不能直接输入中文。</p><p>解决方法有两种。</p><ul><li>搭建动态博客。给动态博客里面加入一个编辑器,这样在手机或平板上就可以进行编辑,没有 Hexo 环境也没关系。</li><li>使用 Metaweblog API 。一些写作软件,如 <a href="https://zh.mweb.im/" target="_blank" rel="noopener">MWeb</a> 等都支持这个 API 。但是需要我们实现一套这样的 API,才能使用。</li></ul><p>本文主要介绍如何使用 Express 实现这套接口。</p><h1 id="依赖库"><a href="#依赖库" class="headerlink" title="依赖库"></a>依赖库</h1><p>以下是主要用到的依赖库:</p><ul><li>express</li><li>fs-extra</li><li>simple-git</li><li>hexo</li><li>md5</li></ul><h1 id="主要接口"><a href="#主要接口" class="headerlink" title="主要接口"></a>主要接口</h1><p>MWeb 调用了两种接口,一种是 Metaweblog API ,一种是 Blogger API 。下面分别介绍参数</p><h2 id="Metaweblog-API"><a href="#Metaweblog-API" class="headerlink" title="Metaweblog API"></a>Metaweblog API</h2><p>下面列出的是接口的参数:</p><table><thead><tr><th>接口</th><th>说明</th><th>参数</th><th>类型</th><th>参数说明</th></tr></thead><tbody><tr><td><code>newPost</code></td><td>新增一篇博客</td><td><code>blogid</code></td><td><code>String</code></td><td>博客 ID ,适用于一个网站很多博客的那种(如 CSDN )</td></tr><tr><td></td><td></td><td><code>username</code></td><td><code>String</code></td><td>用户名</td></tr><tr><td></td><td></td><td><code>password</code></td><td><code>String</code></td><td>密码</td></tr><tr><td></td><td></td><td><code>post</code></td><td><code>PostContent</code></td><td>文章内容,具体类型详见下面的代码</td></tr><tr><td><code>getPost</code></td><td>获取一篇博客</td><td><code>postid</code></td><td><code>String</code></td><td>文章 ID ,由 <code>newPost</code> 接口返回</td></tr><tr><td></td><td></td><td><code>username</code></td><td><code>String</code></td><td></td></tr><tr><td></td><td></td><td><code>password</code></td><td><code>String</code></td><td></td></tr><tr><td><code>getCategories</code></td><td>获取博客的分类</td><td><code>blogid</code></td><td><code>String</code></td><td></td></tr><tr><td></td><td></td><td><code>username</code></td><td><code>String</code></td><td></td></tr><tr><td></td><td></td><td><code>password</code></td><td><code>String</code></td><td></td></tr><tr><td><code>newMediaObject</code></td><td>添加多媒体文件</td><td><code>blogid</code></td><td><code>String</code></td><td></td></tr><tr><td></td><td></td><td><code>username</code></td><td><code>String</code></td><td></td></tr><tr><td></td><td></td><td><code>password</code></td><td><code>String</code></td><td></td></tr><tr><td></td><td></td><td><code>mediaObject</code></td><td><code>MediaObject</code></td><td>多媒体文件,具体类型详见下面的代码</td></tr></tbody></table><p>上面两个结构体的定义( Typescript 描述法):</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> PostContent {</span><br><span class="line"> categories: <span class="built_in">string</span>[];<span class="comment">// 分类</span></span><br><span class="line"> dateCreated: <span class="built_in">Date</span>;<span class="comment">// 创建日期</span></span><br><span class="line"> description: <span class="built_in">string</span>;<span class="comment">// 文章内容</span></span><br><span class="line"> title: <span class="built_in">string</span>;<span class="comment">// 文章标题</span></span><br><span class="line"> mt_keywords: <span class="built_in">string</span>;<span class="comment">// 标签</span></span><br><span class="line"> wp_slug: <span class="built_in">string</span>;<span class="comment">// 自定义 Web 链接</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> MediaObject {</span><br><span class="line"> overwrite: <span class="built_in">boolean</span>;</span><br><span class="line"> bits: Buffer;<span class="comment">// 多媒体二进制数据</span></span><br><span class="line"> name: <span class="built_in">string</span>;<span class="comment">// 文件名</span></span><br><span class="line"> <span class="keyword">type</span>: <span class="built_in">string</span>;<span class="comment">// MIME 类型</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> Category {</span><br><span class="line"> categoryid: <span class="built_in">string</span>;<span class="comment">// 分类ID</span></span><br><span class="line"> description: <span class="built_in">string</span>;<span class="comment">// 分类描述</span></span><br><span class="line"> title: <span class="built_in">string</span>;<span class="comment">// 分类名称</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> NewMediaObjectReturnType {</span><br><span class="line"> url: <span class="built_in">string</span>;<span class="comment">// 媒体文件访问地址</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>下面是这几个接口的返回类型:</p><table><thead><tr><th>接口</th><th>返回类型</th><th>说明</th></tr></thead><tbody><tr><td><code>newPost</code></td><td><code>String</code></td><td>返回的是文章的 <code>PostID</code></td></tr><tr><td><code>getPost</code></td><td><code>PostContent</code></td><td>返回文章的结构体</td></tr><tr><td><code>getCategories</code></td><td><code>Category[]</code></td><td>返回的是分类的结构体数组</td></tr><tr><td><code>newMediaObject</code></td><td><code>NewMediaObjectReturnType</code></td><td>返回的是表示媒体文件访问信息的结构体</td></tr></tbody></table><h2 id="Blogger-API"><a href="#Blogger-API" class="headerlink" title="Blogger API"></a>Blogger API</h2><p>这里只用到了一个接口</p><table><thead><tr><th>名称</th><th>说明</th><th>参数</th><th>参数类型</th><th>参数说明</th><th>返回类型</th></tr></thead><tbody><tr><td><code>getUserBlogs</code></td><td>获取博客信息</td><td><code>key</code></td><td><code>String</code></td><td>(不用)</td><td><code>GetUserBlogsReturnType</code></td></tr><tr><td></td><td></td><td><code>username</code></td><td><code>String</code></td><td>用户名</td><td></td></tr><tr><td></td><td></td><td><code>password</code></td><td><code>String</code></td><td>密码</td></tr></tbody></table><p>接口返回信息</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> GetUserBlogsReturnType {</span><br><span class="line"> blogid: <span class="built_in">string</span><span class="comment">// 博客 ID</span></span><br><span class="line"> blogName: <span class="built_in">string</span><span class="comment">// 博客名称</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="基于-express-和-xrpc-库的实现方式"><a href="#基于-express-和-xrpc-库的实现方式" class="headerlink" title="基于 express 和 xrpc 库的实现方式"></a>基于 express 和 xrpc 库的实现方式</h1><p>XML-RPC 协议的解析可以利用 xrpc 库进行实现。<code>app.ts</code> 的写法为</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> express <span class="keyword">from</span> <span class="string">'express'</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> path <span class="keyword">from</span> <span class="string">'path'</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> favicon <span class="keyword">from</span> <span class="string">'serve-favicon'</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> logger <span class="keyword">from</span> <span class="string">'morgan'</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> cookieParser <span class="keyword">from</span> <span class="string">'cookie-parser'</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> bodyParser <span class="keyword">from</span> <span class="string">'body-parser'</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> xrpc <span class="keyword">from</span> <span class="string">"xrpc"</span>;</span><br><span class="line"><span class="keyword">import</span> { newPost, getPost, editPost, getCategories, newMediaObject } <span class="keyword">from</span> <span class="string">'./metaweblog'</span>;</span><br><span class="line"><span class="keyword">import</span> { getUserBlogs } <span class="keyword">from</span> <span class="string">'./blogger'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> app = express();</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> ExpressError <span class="keyword">extends</span> <span class="built_in">Error</span> {</span><br><span class="line"> status: <span class="built_in">number</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">app.use(logger(<span class="string">'dev'</span>));</span><br><span class="line">app.use(bodyParser.json());</span><br><span class="line">app.use(bodyParser.urlencoded({ extended: <span class="literal">false</span> }));</span><br><span class="line">app.use(cookieParser());</span><br><span class="line">app.use(express.static(path.join(__dirname, <span class="string">'public'</span>)));</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 增加的 xrpc 部分</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">app.use(xrpc.xmlRpc);</span><br><span class="line">app.post(<span class="string">'/RPC'</span>, xrpc.route({</span><br><span class="line"> metaWeblog: {</span><br><span class="line"> newPost: newPost,</span><br><span class="line"> getPost: getPost,</span><br><span class="line"> getCategories: getCategories,</span><br><span class="line"> newMediaObject: newMediaObject</span><br><span class="line"> },</span><br><span class="line"> blogger: {</span><br><span class="line"> getUsersBlogs: getUserBlogs,</span><br><span class="line"> }</span><br><span class="line">}));</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * RPC</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// catch 404 and forward to error handler</span></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span>(<span class="params">req, res, next</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> err = <span class="keyword">new</span> ExpressError(<span class="string">'Not Found'</span>);</span><br><span class="line"> err.status = <span class="number">404</span>;</span><br><span class="line"> next(err);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// error handler</span></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span>(<span class="params">err, req, res, next</span>) </span>{</span><br><span class="line"> <span class="comment">// set locals, only providing error in development</span></span><br><span class="line"> res.locals.message = err.message;</span><br><span class="line"> res.locals.error = req.app.get(<span class="string">'env'</span>) === <span class="string">'development'</span> ? err : {};</span><br><span class="line"></span><br><span class="line"> <span class="comment">// render the error page</span></span><br><span class="line"> res.status(err.status || <span class="number">500</span>);</span><br><span class="line"> res.render(<span class="string">'error'</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = app;</span><br></pre></td></tr></table></figure><p><code>metaweblog.ts</code> 的实现方式</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> md5 <span class="keyword">from</span> <span class="string">"md5"</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> fs <span class="keyword">from</span> <span class="string">"fs-extra"</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> simplegit <span class="keyword">from</span> <span class="string">"simple-git/promise"</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> path <span class="keyword">from</span> <span class="string">"path"</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> moment <span class="keyword">from</span> <span class="string">"moment"</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> Hexo <span class="keyword">from</span> <span class="string">"hexo"</span>;</span><br><span class="line"><span class="keyword">import</span> { Stream } <span class="keyword">from</span> <span class="string">"stream"</span>;</span><br><span class="line"><span class="keyword">const</span> config = <span class="built_in">require</span>(<span class="string">"./package.json"</span>);</span><br><span class="line"><span class="keyword">const</span> blog = config.meta.blog</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> ExpressError <span class="keyword">extends</span> <span class="built_in">Error</span> {</span><br><span class="line"> status: <span class="built_in">number</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">constructor</span>(<span class="params">msg, code</span>) {</span><br><span class="line"> <span class="keyword">super</span>(msg);</span><br><span class="line"> <span class="keyword">this</span>.status = code;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> hexo = <span class="keyword">new</span> Hexo(blog, {</span><br><span class="line"> silent: <span class="literal">true</span>,</span><br><span class="line"> safe: <span class="literal">true</span></span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> PostContent {</span><br><span class="line"> categories: <span class="built_in">string</span>[];</span><br><span class="line"> dateCreated: <span class="built_in">Date</span>;</span><br><span class="line"> description: <span class="built_in">string</span>;</span><br><span class="line"> title: <span class="built_in">string</span>;</span><br><span class="line"> mt_keywords: <span class="built_in">string</span>;</span><br><span class="line"> wp_slug: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> MediaObject {</span><br><span class="line"> overwrite: <span class="built_in">boolean</span>;</span><br><span class="line"> bits: Buffer;</span><br><span class="line"> name: <span class="built_in">string</span>;</span><br><span class="line"> <span class="keyword">type</span>: <span class="built_in">string</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">newPost</span>(<span class="params">blogid: <span class="built_in">string</span>, username: <span class="built_in">string</span>, password: <span class="built_in">string</span>, post: PostContent, publish: <span class="built_in">boolean</span>, callback: <span class="built_in">Function</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (blogid) {</span><br><span class="line"> <span class="keyword">if</span> (username.toLowerCase() === <span class="string">"hpdell"</span> && md5(password) === <span class="string">"2b759a6996d878c41bb7d56ce530d031"</span>) {</span><br><span class="line"> <span class="keyword">const</span> git = simplegit(blog);</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">await</span> git.checkIsRepo()) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> postInfo = (<span class="keyword">await</span> fs.readFile(path.resolve(path.join(blog, <span class="string">"scaffolds"</span>, <span class="string">"post.md"</span>)))).toString();</span><br><span class="line"> postInfo = postInfo.replace(<span class="string">"{{ title }}"</span>, post.title);</span><br><span class="line"> postInfo = postInfo.replace(<span class="string">"{{ date }}"</span>, moment(post.dateCreated).format(<span class="string">"YYYY-MM-DD HH:mm:ss"</span>));</span><br><span class="line"> <span class="keyword">if</span> (post.categories && post.categories.length) {</span><br><span class="line"> postInfo = postInfo.replace(<span class="string">"categories:"</span>, <span class="string">`categories: <span class="subst">${post.categories[0]}</span>`</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (post.mt_keywords) {</span><br><span class="line"> <span class="keyword">let</span> tags = post.mt_keywords.split(<span class="string">","</span>);</span><br><span class="line"> <span class="keyword">let</span> tagInfo = tags.map(<span class="function"><span class="params">item</span> =></span> <span class="string">` - <span class="subst">${item}</span>`</span>).join(<span class="string">"\n"</span>);</span><br><span class="line"> postInfo = postInfo.replace(<span class="string">"tags:"</span>, <span class="string">`tags:\n<span class="subst">${tagInfo}</span>`</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">let</span> content = postInfo + post.description;</span><br><span class="line"> <span class="keyword">let</span> postPath = path.resolve(path.join(blog, <span class="string">"source"</span>, <span class="string">"_posts"</span>, <span class="string">`<span class="subst">${post.wp_slug}</span>.md`</span>));</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">await</span> fs.writeFile(postPath, content);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">await</span> publishToGitHub(git, path.join(<span class="string">"source"</span>, <span class="string">"_posts"</span>, <span class="string">`<span class="subst">${post.wp_slug}</span>.md`</span>));</span><br><span class="line"> callback(<span class="literal">null</span>, post.wp_slug);</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> callback(<span class="keyword">new</span> ExpressError((error <span class="keyword">as</span> <span class="built_in">Error</span>).message, <span class="number">500</span>));</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> callback(<span class="keyword">new</span> ExpressError(<span class="string">"Internal Server Error: Cannot write post file."</span>, <span class="number">500</span>));</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> callback(<span class="keyword">new</span> ExpressError(<span class="string">"Internal Server Error: Cannot read scaffolds."</span>, <span class="number">500</span>));</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> callback(<span class="keyword">new</span> ExpressError(<span class="string">"Inernal Server Error: Not a hexo repo."</span>, <span class="number">500</span>));</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> callback(<span class="keyword">new</span> ExpressError(<span class="string">"Username or Password is wrong."</span>, <span class="number">500</span>));</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> callback(<span class="keyword">new</span> ExpressError(<span class="string">"Blog id is required."</span>, <span class="number">500</span>));</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">getPost</span>(<span class="params">postid: <span class="built_in">string</span>, username: <span class="built_in">string</span>, password: <span class="built_in">string</span>, callback: <span class="built_in">Function</span></span>) </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">await</span> hexo.load()</span><br><span class="line"> <span class="keyword">let</span> posts = hexo.locals.get(<span class="string">"posts"</span>).filter(<span class="function">(<span class="params">v, i</span>) =></span> v.title === postid).toArray();</span><br><span class="line"> <span class="keyword">if</span> (posts && posts.length) {</span><br><span class="line"> <span class="keyword">let</span> post = posts[<span class="number">0</span>];</span><br><span class="line"> <span class="keyword">let</span> postStructure: PostContent = {</span><br><span class="line"> categories: post.categories,</span><br><span class="line"> dateCreated: post.date.toDate(),</span><br><span class="line"> description: post.content,</span><br><span class="line"> title: post.title,</span><br><span class="line"> mt_keywords: post.tags.join(<span class="string">","</span>),</span><br><span class="line"> wp_slug: path.basename(post.path)</span><br><span class="line"> };</span><br><span class="line"> callback(<span class="literal">null</span>, postStructure)</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> callback(<span class="keyword">new</span> ExpressError(<span class="string">"Post not found"</span>, <span class="number">500</span>));</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">getCategories</span>(<span class="params">blogid: <span class="built_in">string</span>, username: <span class="built_in">string</span>, password: <span class="built_in">string</span>, callback: <span class="built_in">Function</span></span>) </span>{</span><br><span class="line"> callback(<span class="literal">null</span>, config.meta.categories.map(<span class="function">(<span class="params">item: <span class="built_in">string</span></span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> {</span><br><span class="line"> categoryid: item,</span><br><span class="line"> description: item,</span><br><span class="line"> title: item</span><br><span class="line"> }</span><br><span class="line"> }));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">newMediaObject</span>(<span class="params">blogid: <span class="built_in">string</span>, username: <span class="built_in">string</span>, password: <span class="built_in">string</span>, mediaObject: MediaObject, callback: <span class="built_in">Function</span></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (blogid) {</span><br><span class="line"> <span class="keyword">if</span> (username.toLowerCase() === <span class="string">"hpdell"</span> && md5(password) === <span class="string">"2b759a6996d878c41bb7d56ce530d031"</span>) {</span><br><span class="line"> <span class="keyword">let</span> imgPath = path.join(blog, <span class="string">"source"</span>, <span class="string">"assets"</span>, <span class="string">"img"</span>, mediaObject.name);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">await</span> fs.writeFile(imgPath, mediaObject.bits);</span><br><span class="line"> callback(<span class="literal">null</span>, {</span><br><span class="line"> url: <span class="string">"/"</span> + [<span class="string">"assets"</span>, <span class="string">"img"</span>, mediaObject.name].join(<span class="string">"/"</span>)</span><br><span class="line"> })</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> callback(<span class="keyword">new</span> ExpressError(<span class="string">"Writefile Wrong."</span>, <span class="number">500</span>));</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> callback(<span class="keyword">new</span> ExpressError(<span class="string">"Username or Password is wrong."</span>, <span class="number">500</span>));</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> callback(<span class="keyword">new</span> ExpressError(<span class="string">"Blog id is required."</span>, <span class="number">500</span>));</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">publishToGitHub</span>(<span class="params">git: simplegit.SimpleGit, postPath</span>) </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">let</span> imgDirPath = path.join(<span class="string">"source"</span>, <span class="string">"assets"</span>, <span class="string">"img"</span>, <span class="string">"*"</span>);</span><br><span class="line"> <span class="keyword">await</span> git.add([postPath, imgDirPath]);</span><br><span class="line"> <span class="keyword">await</span> git.commit(<span class="string">`Add Post: <span class="subst">${path.basename(postPath)}</span>`</span>);</span><br><span class="line"> <span class="keyword">await</span> git.pull(<span class="string">"origin"</span>, <span class="string">"master"</span>, {</span><br><span class="line"> <span class="string">"--rebase"</span>: <span class="string">"true"</span></span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">await</span> git.push();</span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"git error"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>这个实现还比较粗糙,为了简单起见, <code>getPost</code> 还是调用了 Hexo 的接口。分类部分还是用的 package.json 文件中确定的几种类型。对 blogid 也没有做判断。</p></blockquote><p><code>blogger.ts</code> 的实现方式</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">getUserBlogs</span>(<span class="params">key: <span class="built_in">string</span>, username: <span class="built_in">string</span>, password: <span class="built_in">string</span>, callback: <span class="built_in">Function</span></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'getuserblogs called with key:'</span>, key, <span class="string">'username:'</span>, username, <span class="string">'and password:'</span>, password);</span><br><span class="line"> callback(<span class="literal">null</span>, [{ blogid: <span class="string">'hpdell-hexo'</span>, blogName: <span class="string">'HPDell 的 Hexo 博客'</span>, }]);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>这里这个实现也是为了简单起见,直接返回了固定的名称和 ID 。</p></blockquote><p>至此,这个接口就算是基本实现了。</p><h1 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h1><p>查看 <a href="https://hpdell.github.io/">我的博客</a> 里面有两篇博文</p><ul><li>测试发布到 GitHub</li><li>测试通过 Metaweblog API 发图片</li></ul><p>即可查看效果。</p>]]></content>
<summary type="html">
<h1 id="Hexo-搭建静态博客的问题"><a href="#Hexo-搭建静态博客的问题" class="headerlink" title="Hexo 搭建静态博客的问题"></a>Hexo 搭建静态博客的问题</h1><p>Hexo + GitHub Pages 搭建
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="TypeScript" scheme="http://hpdell.github.io/tags/TypeScript/"/>
<category term="Node.js" scheme="http://hpdell.github.io/tags/Node-js/"/>
<category term="博客相关" scheme="http://hpdell.github.io/tags/%E5%8D%9A%E5%AE%A2%E7%9B%B8%E5%85%B3/"/>
</entry>
<entry>
<title>测试发布到 GitHub</title>
<link href="http://hpdell.github.io/%E5%85%B6%E4%BB%96/test-metaweblog-api/"/>
<id>http://hpdell.github.io/其他/test-metaweblog-api/</id>
<published>2019-02-26T13:10:55.000Z</published>
<updated>2022-04-14T16:50:55.573Z</updated>
<content type="html"><![CDATA[<h1 id="测试一篇测试博客"><a href="#测试一篇测试博客" class="headerlink" title="测试一篇测试博客"></a>测试一篇测试博客</h1><p>测试通过 MetaWeblog API 发布博客。</p>]]></content>
<summary type="html">
<h1 id="测试一篇测试博客"><a href="#测试一篇测试博客" class="headerlink" title="测试一篇测试博客"></a>测试一篇测试博客</h1><p>测试通过 MetaWeblog API 发布博客。</p>
</summary>
<category term="其他" scheme="http://hpdell.github.io/categories/%E5%85%B6%E4%BB%96/"/>
<category term="测试" scheme="http://hpdell.github.io/tags/%E6%B5%8B%E8%AF%95/"/>
</entry>
<entry>
<title>测试通过 MetaWeblog API 发图片</title>
<link href="http://hpdell.github.io/%E5%85%B6%E4%BB%96/test-metaweblog-api-image/"/>
<id>http://hpdell.github.io/其他/test-metaweblog-api-image/</id>
<published>2019-02-26T13:10:55.000Z</published>
<updated>2022-04-14T16:50:55.573Z</updated>
<content type="html"><![CDATA[<h1 id="测试一篇测试博客"><a href="#测试一篇测试博客" class="headerlink" title="测试一篇测试博客"></a>测试一篇测试博客</h1><p>测试通过 MetaWeblog API 发布博客。</p><p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/assets/img/15511608490548.jpg" alt=""></p>]]></content>
<summary type="html">
<h1 id="测试一篇测试博客"><a href="#测试一篇测试博客" class="headerlink" title="测试一篇测试博客"></a>测试一篇测试博客</h1><p>测试通过 MetaWeblog API 发布博客。</p>
<p><img src="dat
</summary>
<category term="其他" scheme="http://hpdell.github.io/categories/%E5%85%B6%E4%BB%96/"/>
<category term="测试" scheme="http://hpdell.github.io/tags/%E6%B5%8B%E8%AF%95/"/>
</entry>
<entry>
<title>自己动手写动态博客</title>
<link href="http://hpdell.github.io/%E7%BC%96%E7%A8%8B/dynamic-blog/"/>
<id>http://hpdell.github.io/编程/dynamic-blog/</id>
<published>2019-02-24T20:34:46.000Z</published>
<updated>2022-04-14T16:50:55.429Z</updated>
<content type="html"><![CDATA[<p>我自己动手写了一个基于 <code>Quasar</code> <code>Express</code> 的动态博客,使用 <code>TypeScript</code> 编写。博客代码放在 GitHub 上,目前是私有库状态。</p><ul><li>后端仓库: <a href="https://github.com/HPDell/blog-zone" target="_blank" rel="noopener">HPDell/blog-zone</a></li><li>前端仓库: <a href="https://github.com/HPDell/blog-zone-views" target="_blank" rel="noopener">HPDell/blog-zone-views</a></li></ul><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/dynamic-blog/dynamic-blog-homepage.png"><p>起初做这个动态博客的原因是,过年想用手机发一篇博客。虽然有 Travis 持续集成,已经为用手机写提供了可能,但整个流程走下来,过程还是太复杂了,而且 GitHub 的编辑器 iOS 不能直接输入中文,导致我只能先在备忘录里面写好。正好趁着练一下以前没有涉足过的技术。(目前博客已经部署,但是由于是自己家的服务器,不太敢放链接出来。)</p><h1 id="依赖库"><a href="#依赖库" class="headerlink" title="依赖库"></a>依赖库</h1><p>前端主要用到的依赖库有:</p><ul><li>Quasar 、 Webpack 和 Vue 系全家桶</li><li>MavonEditor :Markdown 编辑器(由于需要做必要的改造,因此将源仓库克隆,使用克隆后的仓库)</li><li>marked :渲染 Markdown</li><li>Prism.js :代码高亮</li><li>MathJax :渲染数学公式</li><li>abcjs :渲染乐谱</li><li>除此之外还有 jquery 、 md5 、 moment 、 markdown-loader 、 html-loader 等</li></ul><p>后端主要用到的依赖库有:</p><ul><li>Express</li><li>Typeorm</li><li>Moment.js</li><li>Sqlite3</li><li>JsonWebToken</li><li>uuid</li></ul><h1 id="部署方法"><a href="#部署方法" class="headerlink" title="部署方法"></a>部署方法</h1><h2 id="后端的部署"><a href="#后端的部署" class="headerlink" title="后端的部署"></a>后端的部署</h2><p>后端的安装比较简单。克隆仓库后,使用如下流程进行部署</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">yarn global add typescript <span class="comment"># 如果没有 TypeScript 环境</span></span><br><span class="line">yarn</span><br><span class="line">tsc</span><br><span class="line"></span><br><span class="line"><span class="comment"># 直接启动 Node 进程</span></span><br><span class="line">node app.js</span><br><span class="line"><span class="comment"># 或推荐使用 PM2 管理进程</span></span><br><span class="line">pm2 start app.js --name blog-zone</span><br></pre></td></tr></table></figure><p>部署完后,可以通过调用一次 <code>POST /login/register</code> 接口,来为数据库添加<strong>一个</strong>管理员用户。由于没有开放注册接口,因此对通过接口进行注册进行了限制。如果想修改这个限制到 <code>${maxUserNumber}</code>,对 <code>/routes/login.ts</code> 进行修改:</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">router.post("/register", async function (req: Request, res: Response) {</span><br><span class="line"> const connection = getConnection();</span><br><span class="line"> try {</span><br><span class="line"> let userList = await connection.getRepository(User).find();</span><br><span class="line"><span class="deletion">- if (userList.length < 1) { </span></span><br><span class="line"><span class="addition">+ if (userList.length < ${maxUserNumber}) { </span></span><br><span class="line"> let userInfo = new User();</span><br><span class="line"> userInfo.name = req.body.username;</span><br><span class="line"> userInfo.password = req.body.password;</span><br><span class="line"> userInfo.description = req.body.description;</span><br><span class="line"> try {</span><br><span class="line"> let user = await connection.manager.save(userInfo);</span><br><span class="line"> res.json(user);</span><br><span class="line"> } catch (error) {</span><br><span class="line"> console.log(error);</span><br><span class="line"> res.sendStatus(500);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } catch (error) {</span><br><span class="line"> console.log(error);</span><br><span class="line"> res.sendStatus(500);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>日后会将这个配置加入到 <code>package.json</code> 中。</p><h2 id="前端的部署"><a href="#前端的部署" class="headerlink" title="前端的部署"></a>前端的部署</h2><p>前端的安装也比较简单。克隆仓库后,按如下流程部署</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">yarn global add quasar-cli</span><br><span class="line">yarn</span><br><span class="line">quasar build</span><br></pre></td></tr></table></figure><p>将 <code>dist</code> 文件夹生成的文件用服务器(如 Nginx)进行代理,即可访问。</p><p>剩下的就是使用 Nginx 对后台进行反向代理了。配置脚本如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">server {</span><br><span class="line"> listen 80;</span><br><span class="line"></span><br><span class="line"> server_name $domain;</span><br><span class="line"></span><br><span class="line"> location /api/ {</span><br><span class="line"> proxy_pass http://localhost:3000/api/;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> location /login/ {</span><br><span class="line"> proxy_pass http://localhost:3000/login/;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> location / {</span><br><span class="line"> root $pathToDist;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="主要配置选项"><a href="#主要配置选项" class="headerlink" title="主要配置选项"></a>主要配置选项</h1><p>后端的数据库是可选的,参考 <a href="http://typeorm.io/#/connection-options" target="_blank" rel="noopener">Typorm 的配置</a> 。默认采用的是 SQLite 数据库,数据库文件是根目录的 <code>database.db</code> 文件。</p><p>前端主要有以下几个配置:</p><ul><li><code>package.json</code> 文件<ul><li><code>meta</code> 属性<ul><li><code>owner</code> : 博客显示的名称(即显示 <code>${owenr} 的博客</code> ,<code>${owner} 的博文</code> 等)</li><li><code>description</code> : 博客显示的简介</li></ul></li></ul></li><li><code>/src/components/welcome.md</code> 文件:这个文件记录了主页显示的内容。将来可能移动到专门的地方,或者放在服务器中。</li></ul><h1 id="支持的内容"><a href="#支持的内容" class="headerlink" title="支持的内容"></a>支持的内容</h1><p>博客支持以下内容</p><ul><li>微文:一般用于撰写小段独立文字,配图最多 9 张,而且也是独立于文字显示的。</li><li>博文:一般用于长文写作,需要提供标题。</li></ul><p>微文和博文都支持 Markdown 语法。本博客对 Markdown 的支持进行了扩展,拥有以下功能</p><ul><li>代码:语法高亮、行号、显示语言。</li><li>数学公式:支持 LaTeX 格式编写的数学公式。</li><li>乐谱:支持 <a href="http://abcnotation.com/" target="_blank" rel="noopener">ABC</a> 格式书写的乐谱,同时显示乐谱和 MIDI 音频。</li></ul><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==" data-original="/编程/dynamic-blog/markdown-abc.png"><blockquote><p><strong>注意</strong>:微文支持 Markdown 语法,表明微文中可以插入图片。单一般不建议在微文文字中插入图片。这样插入的图片不支持点击查看大图。</p></blockquote><hr><p>如果你喜欢本项目,欢迎在您心理点个赞。</p>]]></content>
<summary type="html">
<p>我自己动手写了一个基于 <code>Quasar</code> <code>Express</code> 的动态博客,使用 <code>TypeScript</code> 编写。博客代码放在 GitHub 上,目前是私有库状态。</p>
<ul>
<li>后端仓库: <a
</summary>
<category term="编程" scheme="http://hpdell.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="TypeScript" scheme="http://hpdell.github.io/tags/TypeScript/"/>
<category term="网页开发" scheme="http://hpdell.github.io/tags/%E7%BD%91%E9%A1%B5%E5%BC%80%E5%8F%91/"/>
<category term="Node.js" scheme="http://hpdell.github.io/tags/Node-js/"/>
</entry>
</feed>