splatone 0.0.32 → 0.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -161
- package/crawler.js +88 -29
- package/package.json +1 -1
- package/providers/flickr/index.js +56 -9
- package/providers/flickr/worker.js +26 -0
- package/providers/gmap/index.js +221 -0
- package/providers/gmap/worker.js +242 -0
- package/providers/overpass/index.js +209 -0
- package/providers/overpass/worker.js +307 -0
- package/views/index.ejs +23 -10
- package/visualizer/bulky/web.js +15 -5
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
- [Splatone - Multi-layer Composite Heatmap](#splatone---multi-layer-composite-heatmap)
|
|
5
5
|
- [概要](#概要)
|
|
6
6
|
- [Change Log](#change-log)
|
|
7
|
+
- [v0.0.22 → v0.0.33](#v0022--v0033)
|
|
7
8
|
- [v0.0.29 → → v0.0.32](#v0029---v0032)
|
|
8
9
|
- [v0.0.28 → v0.0.29](#v0028--v0029)
|
|
9
10
|
- [v0.0.23 → → v0.0.28](#v0023--v0028)
|
|
10
11
|
- [v0.0.22 → → v0.0.23](#v0022--v0023)
|
|
11
12
|
- [使い方](#使い方)
|
|
12
|
-
- [Helpの表示](#helpの表示)
|
|
13
13
|
- [最小コマンド例](#最小コマンド例)
|
|
14
14
|
- [ブラウズ専用モード](#ブラウズ専用モード)
|
|
15
15
|
- [インタラクティブモード](#インタラクティブモード)
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
- [コマンドライン引数](#コマンドライン引数)
|
|
21
21
|
- [GimmeGimmeモードで取得する写真とそのファイル名について](#gimmegimmeモードで取得する写真とそのファイル名について)
|
|
22
22
|
- [Flickr APIキーの与え方](#flickr-apiキーの与え方)
|
|
23
|
+
- [gmap: Google Places Text Searchを取得するクローラー](#gmap-google-places-text-searchを取得するクローラー)
|
|
24
|
+
- [overpass: OpenStreetMapの地点を取得するクローラー](#overpass-openstreetmapの地点を取得するクローラー)
|
|
23
25
|
- [Visualizer (可視化モジュール)](#visualizer-可視化モジュール)
|
|
24
26
|
- [Bulky: 全ての点を地図上にポイントする](#bulky-全ての点を地図上にポイントする)
|
|
25
27
|
- [コマンド例](#コマンド例)
|
|
@@ -61,9 +63,11 @@
|
|
|
61
63
|
|
|
62
64
|
## <a name=''></a>概要
|
|
63
65
|
|
|
64
|
-
SNS
|
|
66
|
+
SNSのジオタグ付きポストをキーワードに基づいて収集するツールです。キーワードは複数指定し、それぞれのキーワードの出現分布を地図上にマップします。現在は以下のソースに対応しています。
|
|
65
67
|
|
|
66
|
-
- Flickr
|
|
68
|
+
- Flickr (provider名: flickr)
|
|
69
|
+
- Google Places Text Search (provider名: gmap)
|
|
70
|
+
- Overpass API / OpenStreetMap (provider名: overpass)
|
|
67
71
|
|
|
68
72
|
集めたデータはキーワード毎に色分けされ地図上で可視化されます。以下の可視化手法に対応しています。
|
|
69
73
|
|
|
@@ -79,10 +83,17 @@ SNSのジオタグ付きポストをキーワードに基づいて収集する
|
|
|
79
83
|
|
|
80
84
|
## <a name='ChangeLog'></a>Change Log
|
|
81
85
|
|
|
86
|
+
### <a name='v0.0.29v0.0.32'></a>v0.0.22 → v0.0.33
|
|
87
|
+
|
|
88
|
+
* Google Maps Place APIからvenueをクローリングするProviderを実装: ```-p gmap```
|
|
89
|
+
* OpenStreetMap Overpass APIからvenueをクローリングするProviderを実装: ```-p overpass```
|
|
90
|
+
|
|
82
91
|
### <a name='v0.0.29v0.0.32'></a>v0.0.29 → → v0.0.32
|
|
83
92
|
|
|
84
93
|
* BrowseモードにURL読み込み機能(デモモード)追加
|
|
85
94
|
* GitHub上に東京タワーとスカイツリーを例としてすべての可視化結果を掲載
|
|
95
|
+
* gmapプロバイダ追加: Google Places Text Search APIから地点を取得
|
|
96
|
+
* overpassプロバイダ追加: Overpass APIからOpenStreetMapのPOIを取得
|
|
86
97
|
|
|
87
98
|
### <a name='v0.0.28v0.0.29'></a>v0.0.28 → v0.0.29
|
|
88
99
|
|
|
@@ -117,164 +128,6 @@ SNSのジオタグ付きポストをキーワードに基づいて収集する
|
|
|
117
128
|
- [Node.js](https://nodejs.org/ja/download)をインストール後、npxで実行します。
|
|
118
129
|
- npxはnpm上のモジュールをコマンド一つでインストールと実行を行う事ができるコマンドです。
|
|
119
130
|
|
|
120
|
-
## <a name='Help'></a>Helpの表示
|
|
121
|
-
|
|
122
|
-
```shell
|
|
123
|
-
$ npx -y -p splatone@latest crawler --help
|
|
124
|
-
[app] [provider] loaded: flickr@1.0.0
|
|
125
|
-
使い方: crawler.js [options]
|
|
126
|
-
|
|
127
|
-
Basic Options
|
|
128
|
-
-p, --provider 実行するプロバイダ [文字列] [選択してください: "flickr"]
|
|
129
|
-
-k, --keywords 検索キーワード(|区切り) [文字列] [デフォルト:
|
|
130
|
-
"nature,tree,flower|building,house|water,sea,river,pond"]
|
|
131
|
-
-f, --filed 大きなデータをファイルとして送受信する
|
|
132
|
-
[真偽] [デフォルト: true]
|
|
133
|
-
-c, --chopped 大きなデータを細分化して送受信する
|
|
134
|
-
[非推奨] [真偽] [デフォルト: false]
|
|
135
|
-
--browse-mode ブラウズ専用モード(範囲描画とクロールを無効化)
|
|
136
|
-
[真偽] [デフォルト: false]
|
|
137
|
-
|
|
138
|
-
Debug
|
|
139
|
-
--debug-verbose デバッグ情報出力 [真偽] [デフォルト: false]
|
|
140
|
-
|
|
141
|
-
UI Defaults
|
|
142
|
-
--ui-cell-size 起動時にUIへ設定するセルサイズ (0で自動)
|
|
143
|
-
[数値] [デフォルト: 0]
|
|
144
|
-
--ui-units セルサイズの単位 (kilometers/meters/miles)
|
|
145
|
-
[文字列] [選択してください: "kilometers", "meters", "miles"] [デフォルト:
|
|
146
|
-
"kilometers"]
|
|
147
|
-
--ui-bbox UI初期表示の矩形範囲。"minLon,minLat,maxLon,maxLat" の形式
|
|
148
|
-
[文字列]
|
|
149
|
-
--ui-polygon UI初期表示のポリゴン。Polygon/MultiPolygonを含むGeoJSON文
|
|
150
|
-
字列 [文字列]
|
|
151
|
-
--city 起動時に中心付近を合わせる都市名(例: "Tokyo") [文字列]
|
|
152
|
-
|
|
153
|
-
For flickr Provider
|
|
154
|
-
--p-flickr-APIKEY Flickr ServiceのAPI KEY [文字列]
|
|
155
|
-
--p-flickr-Extras カンマ区切り/保持する写真のメタデータ(デフォルト値
|
|
156
|
-
は記載の有無に関わらず保持)
|
|
157
|
-
[文字列] [デフォルト: "date_upload,date_taken,owner_name,geo,url_sq,tags"]
|
|
158
|
-
--p-flickr-DateMode 利用時間軸(update=Flickr投稿日時/taken=写真撮影日時
|
|
159
|
-
)
|
|
160
|
-
[選択してください: "upload", "taken"] [デフォルト: "upload"]
|
|
161
|
-
--p-flickr-Haste 時間軸分割並列処理 [真偽] [デフォルト: true]
|
|
162
|
-
--p-flickr-GimmeGimme Flickr画像を保存するディレクトリパス(指定しない場合
|
|
163
|
-
は保存しない) [文字列]
|
|
164
|
-
--p-flickr-DateMax クローリング期間(最大) UNIX TIMEもしくはYYYY-MM-DD
|
|
165
|
-
[文字列] [デフォルト: 1764679068]
|
|
166
|
-
--p-flickr-DateMin クローリング期間(最小) UNIX TIMEもしくはYYYY-MM-DD
|
|
167
|
-
[文字列] [デフォルト: 1072882800]
|
|
168
|
-
|
|
169
|
-
Visualization (最低一つの指定が必須です)
|
|
170
|
-
--vis-bulky 全データをCircleMarkerとして地図上に表示
|
|
171
|
-
[真偽] [デフォルト: false]
|
|
172
|
-
--vis-dbscan クロール結果をDBSCANクラスタリングし、クラスタの凸包
|
|
173
|
-
をポリゴンで可視化します。[真偽] [デフォルト: false]
|
|
174
|
-
--vis-heat カテゴリ毎に異なるレイヤのヒートマップで可視化(色=
|
|
175
|
-
カテゴリ色、透明度=頻度) [真偽] [デフォルト: false]
|
|
176
|
-
--vis-majority-hex HexGrid内で最も出現頻度が高いカテゴリの色で彩色。Hex
|
|
177
|
-
apartiteモードで6分割パイチャート表示。透明度は全体
|
|
178
|
-
で正規化。 [真偽] [デフォルト: false]
|
|
179
|
-
--vis-marker-cluster マーカークラスターとして地図上に表示
|
|
180
|
-
[真偽] [デフォルト: false]
|
|
181
|
-
--vis-pie-charts Hex中心にカテゴリ割合のPie
|
|
182
|
-
Chartを描画するビジュアライザ
|
|
183
|
-
[真偽] [デフォルト: false]
|
|
184
|
-
--vis-voronoi Hex Grid ボロノイ図 [真偽] [デフォルト: false]
|
|
185
|
-
|
|
186
|
-
For bulky Visualizer
|
|
187
|
-
--v-bulky-Radius Point Markerの半径 | min=0, step=1
|
|
188
|
-
[数値] [デフォルト: 5]
|
|
189
|
-
--v-bulky-Stroke Point Markerの線の有無 [真偽] [デフォルト: true]
|
|
190
|
-
--v-bulky-Weight Point Markerの線の太さ | min=0, step=1
|
|
191
|
-
[数値] [デフォルト: 1]
|
|
192
|
-
--v-bulky-Opacity Point Markerの線の透明度 | min=0, max=1, step=0.05
|
|
193
|
-
[数値] [デフォルト: 1]
|
|
194
|
-
--v-bulky-Filling Point Markerの塗りの有無 [真偽] [デフォルト: true]
|
|
195
|
-
--v-bulky-FillOpacity Point Markerの塗りの透明度 | min=0, max=1,
|
|
196
|
-
step=0.05 [数値] [デフォルト: 0.5]
|
|
197
|
-
|
|
198
|
-
For dbscan Visualizer
|
|
199
|
-
--v-dbscan-Eps DBSCANのeps(クラスタ判定距離) | min=0.01,
|
|
200
|
-
step=0.01 [数値] [デフォルト: 0.6]
|
|
201
|
-
--v-dbscan-MinPts DBSCANのminPts(クラスタ確定に必要な点数) |
|
|
202
|
-
min=1, step=1 [数値] [デフォルト: 6]
|
|
203
|
-
--v-dbscan-Units epsで使用する距離単位
|
|
204
|
-
[文字列] [選択してください: "kilometers", "meters", "miles"] [デフォルト:
|
|
205
|
-
"kilometers"]
|
|
206
|
-
--v-dbscan-StrokeWidth ポリゴン輪郭の太さ | min=0, max=10, step=0.5
|
|
207
|
-
[数値] [デフォルト: 2]
|
|
208
|
-
--v-dbscan-StrokeOpacity ポリゴン輪郭の透明度 | min=0, max=1, step=0.05
|
|
209
|
-
[数値] [デフォルト: 0.9]
|
|
210
|
-
--v-dbscan-FillOpacity ポリゴン塗りの透明度 | min=0, max=1, step=0.05
|
|
211
|
-
[数値] [デフォルト: 0.35]
|
|
212
|
-
--v-dbscan-DashArray LeafletのdashArray指定(例: "4 6") | 例: 例: 4
|
|
213
|
-
6 [文字列] [デフォルト: ""]
|
|
214
|
-
--v-dbscan-KernelScale KDEカーネル半径をepsに対して何倍にするか |
|
|
215
|
-
min=0.1, max=10, step=0.1[数値] [デフォルト: 1]
|
|
216
|
-
--v-dbscan-GridSize KDE計算用グリッド解像度(長辺方向セル数) |
|
|
217
|
-
min=8, max=256, step=1 [数値] [デフォルト: 80]
|
|
218
|
-
--v-dbscan-ContourPercent 最大密度に対する等値線レベル(0-1) | min=0.05,
|
|
219
|
-
max=0.95, step=0.05 [数値] [デフォルト: 0.4]
|
|
220
|
-
|
|
221
|
-
For heat Visualizer
|
|
222
|
-
--v-heat-Radius ヒートマップブラーの半径(Unitsで指定した距離単
|
|
223
|
-
位) | min=0, step=1 [数値] [デフォルト: 50]
|
|
224
|
-
--v-heat-Units Radiusに使用する距離単位
|
|
225
|
-
[文字列] [選択してください: "kilometers", "meters", "miles"] [デフォルト:
|
|
226
|
-
"meters"]
|
|
227
|
-
--v-heat-MinOpacity ヒートマップの最小透明度 | min=0, max=1,
|
|
228
|
-
step=0.05 [数値] [デフォルト: 0]
|
|
229
|
-
--v-heat-MaxOpacity ヒートマップの最大透明度 | min=0, max=1,
|
|
230
|
-
step=0.05 [数値] [デフォルト: 1]
|
|
231
|
-
--v-heat-MaxValue ヒートマップ強度の最大値
|
|
232
|
-
(未指定時はデータから自動推定) | step=1 [数値]
|
|
233
|
-
--v-heat-WeightThreshold 半径内の近傍点数(自分以外)がこの値未満の点は描
|
|
234
|
-
画しない | min=0, step=1 [数値] [デフォルト: 1]
|
|
235
|
-
|
|
236
|
-
For majority-hex Visualizer
|
|
237
|
-
--v-majority-hex-Hexapartite 中のカテゴリの頻度に応じて六角形を分割色彩
|
|
238
|
-
[真偽] [デフォルト: false]
|
|
239
|
-
--v-majority-hex-HexOpacity 六角形の線の透明度 | min=0, max=1, step=0.05
|
|
240
|
-
[数値] [デフォルト: 1]
|
|
241
|
-
--v-majority-hex-HexWeight 六角形の線の太さ | min=0, step=1
|
|
242
|
-
[数値] [デフォルト: 1]
|
|
243
|
-
--v-majority-hex-MaxOpacity 正規化後の最大塗り透明度 | min=0, max=1,
|
|
244
|
-
step=0.05 [数値] [デフォルト: 0.9]
|
|
245
|
-
--v-majority-hex-MinOpacity 正規化後の最小塗り透明度 | min=0, max=1,
|
|
246
|
-
step=0.05 [数値] [デフォルト: 0.5]
|
|
247
|
-
|
|
248
|
-
For marker-cluster Visualizer
|
|
249
|
-
--v-marker-cluster-MaxClusterRadius クラスタを構成する範囲(半径) | min=1,
|
|
250
|
-
step=1 [数値] [デフォルト: 80]
|
|
251
|
-
|
|
252
|
-
For pie-charts Visualizer
|
|
253
|
-
--v-pie-charts-MaxRadiusScale Hex内接円半径に対する最大半径スケール
|
|
254
|
-
(0-1.5) | min=0.1, max=1.5, step=0.05
|
|
255
|
-
[数値] [デフォルト: 0.9]
|
|
256
|
-
--v-pie-charts-MinRadiusScale 最大半径に対する最小半径スケール (0-1) |
|
|
257
|
-
min=0, max=1, step=0.05
|
|
258
|
-
[数値] [デフォルト: 0.25]
|
|
259
|
-
--v-pie-charts-StrokeWidth Pie Chart輪郭線の太さ(px) | min=0,
|
|
260
|
-
step=1 [数値] [デフォルト: 1]
|
|
261
|
-
--v-pie-charts-BackgroundOpacity 最大半径ガイドリングの透明度 (0-1) |
|
|
262
|
-
min=0, max=1, step=0.05
|
|
263
|
-
[数値] [デフォルト: 0.2]
|
|
264
|
-
|
|
265
|
-
For voronoi Visualizer
|
|
266
|
-
--v-voronoi-MaxSitesPerHex ポワソン分布に基づいて各ヘックス内でサン
|
|
267
|
-
プリングされる最大サイト数 (0 = 無制限)
|
|
268
|
-
| min=0, step=1 [数値] [デフォルト: 0]
|
|
269
|
-
--v-voronoi-MinSiteSpacingMeters 各ヘックス内でサンプリングされたサイト間
|
|
270
|
-
の最小距離をメートル単位で保証 (0 =
|
|
271
|
-
無効) | min=0, step=1
|
|
272
|
-
[数値] [デフォルト: 50]
|
|
273
|
-
|
|
274
|
-
オプション:
|
|
275
|
-
--help ヘルプを表示 [真偽]
|
|
276
|
-
--version バージョンを表示 [真偽]
|
|
277
|
-
```
|
|
278
131
|
|
|
279
132
|
## <a name='-1'></a>最小コマンド例
|
|
280
133
|
|
|
@@ -347,6 +200,8 @@ npx -y -p splatone@latest browse \
|
|
|
347
200
|
| ```--p-flickr-GimmeGimme``` | 取得した画像を保存するディレクトリ(未指定時はダウンロードせず/失敗時は同名txtで記録) | 文字列 | |
|
|
348
201
|
| ```--p-flickr-DateMax``` | クローリング期間(最大) UNIX TIMEもしくはYYYY-MM-DD | 文字列 | (動的)現時刻 |
|
|
349
202
|
| ```--p-flickr-DateMin``` | クローリング期間(最小) UNIX TIMEもしくはYYYY-MM-DD | 文字列 | 1072882800 |
|
|
203
|
+
| ```--p-flickr-ThrottleMaxConcurrent``` | Flickr API リクエストの同時実行数 | 数値 | 2 |
|
|
204
|
+
| ```--p-flickr-ThrottleMinTimeMs``` | 連続する Flickr リクエスト間の最小待機時間 (ミリ秒) | 数値 | 500 |
|
|
350
205
|
|
|
351
206
|
#### <a name='GimmeGimme'></a>GimmeGimmeモードで取得する写真とそのファイル名について
|
|
352
207
|
- ```--p-flickr-GimmeGimme=保存ディレクトリのパス```のように指定してください。
|
|
@@ -371,6 +226,43 @@ APIキーは以下の3種類の方法で与える事ができます
|
|
|
371
226
|
- **flickr**の場合は```.API_KEY.flickr```になります。
|
|
372
227
|
- optionや環境変数で与えるよりも優先されます。
|
|
373
228
|
|
|
229
|
+
### <a name='gmap-google-places-text-searchを取得するクローラー'></a>gmap: Google Places Text Searchを取得するクローラー
|
|
230
|
+
|
|
231
|
+
Google Maps Places Text Search API を利用して、テキストクエリに合致する POI を Hex 単位で収集します。`-p gmap` を指定し、`--keywords` で `カテゴリ名=TextSearchクエリ` を与えると、カテゴリ名で彩色しながらクエリ文字列を Google に送信します(例: `-k "カフェ=cafe tokyo|観光=landmark tokyo"`)。検索半径は UI のセルサイズ (`--ui-cell-size` と `--ui-units`) をメートル換算した値を自動適用し、API の上限である 50km を超えないようクランプされます。さらに各 Hex の外接 bbox を `locationbias=rectangle` として付与し、Text Search リクエスト自体が対象 Hex の範囲に収束するよう制御しています(最終結果も Hex 内判定でフィルタ)。
|
|
232
|
+
|
|
233
|
+
| オプション | 説明 | 型 | デフォルト |
|
|
234
|
+
| :-- | :-- | :-- | :-- |
|
|
235
|
+
| `--p-gmap-APIKEY` | Google Places API キー。省略時は `.API_KEY.gmap` もしくは `API_KEY_gmap` 環境変数から読み込み。 | 文字列 | |
|
|
236
|
+
| `--p-gmap-Language` | Places API の `language` パラメータ。 | 文字列 | `ja` |
|
|
237
|
+
| `--p-gmap-MaxPages` | Text Search の最大ページ数 (1〜3)。Google 側の仕様上、最大 3 ページ / 60 件。 | 数値 | 3 |
|
|
238
|
+
| `--p-gmap-ThrottleMaxConcurrent` | Google Places リクエストの同時実行本数。 | 数値 | 2 |
|
|
239
|
+
| `--p-gmap-ThrottleMinTimeMs` | 連続する Places リクエスト間の最小待機時間 (ミリ秒)。 | 数値 | 500 |
|
|
240
|
+
|
|
241
|
+
進捗計算は内部定数 (60 件/Hexカテゴリ) を初期期待値として扱い、`next_page_token` が返らなくなったタイミングで実測件数に合わせて 100% に到達させるハイブリッド方式を用いています。
|
|
242
|
+
|
|
243
|
+
Places API は 2 ページ目以降を取得する際に 2 秒程度の待ち時間が必要なため、内部で自動的に待機してから次ページのジョブを投入します。返却される地点はカテゴリ × Hex 単位で重複除去され、Flickr 由来のデータと同様に全ビジュアライザへそのまま流れます。
|
|
244
|
+
|
|
245
|
+
### <a name='overpass-openstreetmapの地点を取得するクローラー'></a>overpass: OpenStreetMapの地点を取得するクローラー
|
|
246
|
+
|
|
247
|
+
OpenStreetMap の Overpass API を用いて、タグ条件に一致するノード/ウェイ/リレーションを Hex 単位で収集します。`-p overpass` を指定し、`--keywords` で `カテゴリ名=タグ条件` を与えると、カテゴリ毎に Hex bbox 内へ個別の Overpass クエリを投げます。タグ条件は `amenity=restaurant` のような `key=value` 形式を基本とし、複数指定したい場合は `,` で区切って OR 検索します(例: `-k "麺類#D93C3C=amenity=ramen,amenity=noodle_shop"`)。`node:amenity=cafe` のように `node|way|relation:` プレフィックスを付けると特定の幾何種のみを対象にできます。
|
|
248
|
+
|
|
249
|
+
| オプション | 説明 | 型 | デフォルト |
|
|
250
|
+
| :-- | :-- | :-- | :-- |
|
|
251
|
+
| `--p-overpass-Endpoint` | 利用する Overpass API interpreter の URL。 | 文字列 | `https://overpass-api.de/api/interpreter` |
|
|
252
|
+
| `--p-overpass-TimeoutSeconds` | Overpass への 1 リクエストあたりのタイムアウト秒数。 | 数値 | 25 |
|
|
253
|
+
| `--p-overpass-MaxRetries` | HTTP/ネットワークエラー時にリトライする最大回数。 | 数値 | 3 |
|
|
254
|
+
| `--p-overpass-UserAgent` | Overpass API に送信する User-Agent。連絡先付きの文字列を推奨。 | 文字列 | `Splatone-Overpass (+https://github.com/YokoyamaLab/Splatone)` |
|
|
255
|
+
| `--p-overpass-ThrottleMaxConcurrent` | Overpass への同時リクエスト最大数。推奨は 1。 | 数値 | 1 |
|
|
256
|
+
| `--p-overpass-ThrottleMinTimeMs` | 連続リクエスト間の待ち時間 (ミリ秒)。 | 数値 | 1500 |
|
|
257
|
+
|
|
258
|
+
進捗は「カテゴリ内に投入したクエリ数」を 100% とみなし、各クエリのレスポンスを受け取るたびに均等配分で加算します。たとえば 2 カテゴリ × 2 条件 (合計 4 クエリ) の場合、1 クエリ完了ごとに Hex 全体の進捗が 25% ずつ前進します。Overpass は共有リソースであるため、大きな bbox や極端に多いクエリを連続実行する際は時間帯やエンドポイント(市民大・Kumi Systems 等)にも配慮してください。デフォルトでは 1 本ずつ 1.5 秒間隔でシリアライズ送信しますが、必要であれば `--p-overpass-Throttle*` オプションで上書きできます(ただし連続アクセスしすぎないよう注意)。
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
$ npx -y -p splatone@latest crawler -p overpass -k "カフェ#ff5f5f=amenity=cafe,amenity=coffee_shop|文化施設#3366ff=tourism=museum,tourism=gallery" --vis-bulky --city "Kyoto"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
上記例では京都市内の Hex を対象に、カフェ系と文化施設系の OpenStreetMap POI を 2 つのカテゴリで色分けしながら取得します。逓減処理後のジオメトリは Leaflet 上のどのビジュアライザでも利用できます。
|
|
265
|
+
|
|
374
266
|
## <a name='Visualizer'></a>Visualizer (可視化モジュール)
|
|
375
267
|
|
|
376
268
|
### <a name='Bulky:'></a>Bulky: 全ての点を地図上にポイントする
|
package/crawler.js
CHANGED
|
@@ -706,7 +706,8 @@ try {
|
|
|
706
706
|
hex: cloneJsonSafe(target.hex ?? null),
|
|
707
707
|
triangles: cloneJsonSafe(target.triangles ?? null),
|
|
708
708
|
categories: cloneJsonSafe(target.categories ?? {}),
|
|
709
|
-
splatonePalette: cloneJsonSafe(target.splatonePalette ?? {})
|
|
709
|
+
splatonePalette: cloneJsonSafe(target.splatonePalette ?? {}),
|
|
710
|
+
gridMeta: cloneJsonSafe(target.gridMeta ?? null)
|
|
710
711
|
};
|
|
711
712
|
}
|
|
712
713
|
|
|
@@ -769,7 +770,8 @@ try {
|
|
|
769
770
|
hex: target.hex ?? context.hexGrid ?? null,
|
|
770
771
|
triangles: target.triangles ?? context.triangles ?? null,
|
|
771
772
|
categories: target.categories ?? context.categories ?? {},
|
|
772
|
-
splatonePalette: target.splatonePalette ?? payload.palette ?? {}
|
|
773
|
+
splatonePalette: target.splatonePalette ?? payload.palette ?? {},
|
|
774
|
+
gridMeta: target.gridMeta ?? context.gridMeta ?? null
|
|
773
775
|
};
|
|
774
776
|
return {
|
|
775
777
|
version: payload.version ?? 1,
|
|
@@ -786,6 +788,7 @@ try {
|
|
|
786
788
|
hexGrid: normalizedTarget.hex,
|
|
787
789
|
triangles: normalizedTarget.triangles,
|
|
788
790
|
categories: normalizedTarget.categories,
|
|
791
|
+
gridMeta: normalizedTarget.gridMeta ?? null,
|
|
789
792
|
cliOptions: (payload.context && payload.context.cliOptions) ? payload.context.cliOptions : {}
|
|
790
793
|
}
|
|
791
794
|
};
|
|
@@ -797,7 +800,8 @@ try {
|
|
|
797
800
|
hex: cloneJsonSafe(sourceTarget.hex ?? null),
|
|
798
801
|
triangles: cloneJsonSafe(sourceTarget.triangles ?? null),
|
|
799
802
|
categories: cloneJsonSafe(sourceTarget.categories ?? {}),
|
|
800
|
-
splatonePalette: cloneJsonSafe(sourceTarget.splatonePalette ?? rawPayload?.palette ?? {})
|
|
803
|
+
splatonePalette: cloneJsonSafe(sourceTarget.splatonePalette ?? rawPayload?.palette ?? {}),
|
|
804
|
+
gridMeta: cloneJsonSafe(sourceTarget.gridMeta ?? null)
|
|
801
805
|
};
|
|
802
806
|
}
|
|
803
807
|
|
|
@@ -1205,7 +1209,8 @@ try {
|
|
|
1205
1209
|
triangles: targets[req.sessionId].triangles,
|
|
1206
1210
|
sessionId: req.sessionId,
|
|
1207
1211
|
categories: targets[req.sessionId].categories,
|
|
1208
|
-
providerOptions: providersOptions[argv.provider]
|
|
1212
|
+
providerOptions: providersOptions[argv.provider],
|
|
1213
|
+
gridMeta: targets[req.sessionId].gridMeta ?? null
|
|
1209
1214
|
};
|
|
1210
1215
|
await providers.call(argv.provider, 'crawl', workerOptions);
|
|
1211
1216
|
}
|
|
@@ -1230,6 +1235,10 @@ try {
|
|
|
1230
1235
|
return;
|
|
1231
1236
|
}
|
|
1232
1237
|
let { bbox, drawn, cellSize = 0, units = 'kilometers', tags = 'sea,beach|mountain,forest' } = req.query;
|
|
1238
|
+
units = typeof units === 'string' ? units.toLowerCase() : 'kilometers';
|
|
1239
|
+
if (!VALID_UI_UNITS.has(units)) {
|
|
1240
|
+
units = 'kilometers';
|
|
1241
|
+
}
|
|
1233
1242
|
const boundary = String(bbox).split(',').map(Number);
|
|
1234
1243
|
if (cellSize == 0) {
|
|
1235
1244
|
//セルサイズ自動決定
|
|
@@ -1255,14 +1264,22 @@ try {
|
|
|
1255
1264
|
|
|
1256
1265
|
if (bbox) {
|
|
1257
1266
|
if (boundary.length !== 4 || !boundary.every(Number.isFinite)) {
|
|
1258
|
-
|
|
1267
|
+
socket.emit('toast', {
|
|
1268
|
+
text: 'bbox must be "minLon,minLat,maxLon,maxLat"',
|
|
1269
|
+
class: 'error'
|
|
1270
|
+
});
|
|
1271
|
+
return;
|
|
1259
1272
|
}
|
|
1260
1273
|
bboxArray = boundary;
|
|
1261
1274
|
}
|
|
1262
1275
|
|
|
1263
1276
|
const sizeNum = Number(cellSize);
|
|
1264
1277
|
if (!Number.isFinite(sizeNum) || sizeNum <= 0) {
|
|
1265
|
-
|
|
1278
|
+
socket.emit('toast', {
|
|
1279
|
+
text: 'cellSize must be a positive number',
|
|
1280
|
+
class: 'error'
|
|
1281
|
+
});
|
|
1282
|
+
return;
|
|
1266
1283
|
}
|
|
1267
1284
|
|
|
1268
1285
|
//カテゴリ生成
|
|
@@ -1416,7 +1433,8 @@ try {
|
|
|
1416
1433
|
}
|
|
1417
1434
|
crawlers[sessionId] = {};
|
|
1418
1435
|
processing[sessionId] = 0;
|
|
1419
|
-
|
|
1436
|
+
const gridMeta = { cellSize: sizeNum, units };
|
|
1437
|
+
targets[sessionId] = { sessionId, hex: hexFC, triangles: trianglesFC, categories, splatonePalette, gridMeta };
|
|
1420
1438
|
socket.emit("hexgrid", { hex: hexFC, triangles: trianglesFC });
|
|
1421
1439
|
} catch (e) {
|
|
1422
1440
|
console.error(e);
|
|
@@ -1489,6 +1507,7 @@ try {
|
|
|
1489
1507
|
triangles: target?.triangles?.features?.length ?? 0,
|
|
1490
1508
|
categories: Object.keys(target?.categories ?? {}).length,
|
|
1491
1509
|
crawled: 0,
|
|
1510
|
+
progressCompleted: 0,
|
|
1492
1511
|
remaining: 0,
|
|
1493
1512
|
expected: 0,
|
|
1494
1513
|
percent: 0
|
|
@@ -1500,6 +1519,7 @@ try {
|
|
|
1500
1519
|
const hexStats = {
|
|
1501
1520
|
categories: {},
|
|
1502
1521
|
crawled: 0,
|
|
1522
|
+
progressCompleted: 0,
|
|
1503
1523
|
remaining: 0,
|
|
1504
1524
|
expected: 0,
|
|
1505
1525
|
percent: 0
|
|
@@ -1508,34 +1528,44 @@ try {
|
|
|
1508
1528
|
const crawled = info?.ids instanceof Set
|
|
1509
1529
|
? info.ids.size
|
|
1510
1530
|
: Number(info?.crawled) || 0;
|
|
1511
|
-
const
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1531
|
+
const progressCompleted = Number.isFinite(info?.progressCompleted)
|
|
1532
|
+
? info.progressCompleted
|
|
1533
|
+
: crawled;
|
|
1534
|
+
const expectedTotal = Number.isFinite(info?.progressExpected)
|
|
1535
|
+
? info.progressExpected
|
|
1536
|
+
: (Number.isFinite(info?.total) ? Number(info.total) : crawled + (Number(info?.remaining) || 0));
|
|
1537
|
+
const progressRemaining = Number.isFinite(info?.progressRemaining)
|
|
1538
|
+
? Math.max(0, info.progressRemaining)
|
|
1539
|
+
: Math.max(0, expectedTotal - progressCompleted);
|
|
1540
|
+
const percent = expectedTotal === 0 ? 1 : Math.min(1, progressCompleted / Math.max(1, expectedTotal));
|
|
1516
1541
|
hexStats.categories[categoryName] = {
|
|
1517
1542
|
crawled,
|
|
1518
|
-
|
|
1519
|
-
|
|
1543
|
+
progressCompleted,
|
|
1544
|
+
remaining: progressRemaining,
|
|
1545
|
+
total: expectedTotal,
|
|
1520
1546
|
percent,
|
|
1521
1547
|
final: info?.final === true
|
|
1522
1548
|
};
|
|
1523
1549
|
hexStats.crawled += crawled;
|
|
1524
|
-
hexStats.
|
|
1525
|
-
hexStats.
|
|
1550
|
+
hexStats.progressCompleted += progressCompleted;
|
|
1551
|
+
hexStats.remaining += progressRemaining;
|
|
1552
|
+
hexStats.expected += expectedTotal;
|
|
1526
1553
|
}
|
|
1554
|
+
const hexProgressValue = hexStats.progressCompleted || hexStats.crawled;
|
|
1527
1555
|
hexStats.percent = hexStats.expected === 0
|
|
1528
1556
|
? 1
|
|
1529
|
-
: Math.min(1,
|
|
1557
|
+
: Math.min(1, hexProgressValue / Math.max(1, hexStats.expected));
|
|
1530
1558
|
summary.hexes[hexId] = hexStats;
|
|
1531
1559
|
summary.totals.crawled += hexStats.crawled;
|
|
1560
|
+
summary.totals.progressCompleted += hexProgressValue;
|
|
1532
1561
|
summary.totals.remaining += hexStats.remaining;
|
|
1533
1562
|
summary.totals.expected += hexStats.expected;
|
|
1534
1563
|
}
|
|
1535
1564
|
|
|
1565
|
+
const totalProgressValue = summary.totals.progressCompleted || summary.totals.crawled;
|
|
1536
1566
|
summary.totals.percent = summary.totals.expected === 0
|
|
1537
1567
|
? 1
|
|
1538
|
-
: Math.min(1,
|
|
1568
|
+
: Math.min(1, totalProgressValue / Math.max(1, summary.totals.expected));
|
|
1539
1569
|
|
|
1540
1570
|
return summary;
|
|
1541
1571
|
}
|
|
@@ -1585,16 +1615,14 @@ try {
|
|
|
1585
1615
|
}
|
|
1586
1616
|
sessionCrawler[rtn.hexId] ??= {};
|
|
1587
1617
|
sessionCrawler[rtn.hexId][rtn.category] ??= {};
|
|
1588
|
-
sessionCrawler[rtn.hexId][rtn.category].terms ??= {};
|
|
1589
|
-
if (
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
if (sessionCrawler[rtn.hexId][rtn.category].terms[prevTermId] && !sessionCrawler[rtn.hexId][rtn.category].terms[prevTermId].final) {
|
|
1593
|
-
sessionCrawler[rtn.hexId][rtn.category].terms[prevTermId].final = true;
|
|
1594
|
-
sessionCrawler[rtn.hexId][rtn.category].terms[prevTermId].remaining = 0;
|
|
1618
|
+
const termMap = sessionCrawler[rtn.hexId][rtn.category].terms ??= {};
|
|
1619
|
+
if (rtn.prevTermId && termMap[rtn.prevTermId]) {
|
|
1620
|
+
if (!termMap[rtn.prevTermId].final) {
|
|
1621
|
+
termMap[rtn.prevTermId].final = true;
|
|
1595
1622
|
}
|
|
1623
|
+
termMap[rtn.prevTermId].remaining = 0;
|
|
1596
1624
|
}
|
|
1597
|
-
|
|
1625
|
+
termMap[rtn.TermId] ??= {};
|
|
1598
1626
|
sessionCrawler[rtn.hexId][rtn.category].items ??= featureCollection([]);
|
|
1599
1627
|
sessionCrawler[rtn.hexId][rtn.category].ids ??= new Set();
|
|
1600
1628
|
|
|
@@ -1615,17 +1643,48 @@ try {
|
|
|
1615
1643
|
uniqueFeatures.push(feature);
|
|
1616
1644
|
}
|
|
1617
1645
|
|
|
1618
|
-
currentHexCategory.terms[rtn.TermId]
|
|
1619
|
-
|
|
1646
|
+
const termEntry = currentHexCategory.terms[rtn.TermId];
|
|
1647
|
+
termEntry.remaining = rtn.remaining;
|
|
1648
|
+
termEntry.final = rtn.final;
|
|
1649
|
+
const progressDelta = Number(rtn.progressDelta);
|
|
1650
|
+
if (Number.isFinite(progressDelta)) {
|
|
1651
|
+
termEntry.progressCompleted = Math.max(0, (termEntry.progressCompleted ?? 0) + progressDelta);
|
|
1652
|
+
}
|
|
1620
1653
|
if (rtn.photos.features.length >= 250 && duplicateCount === rtn.photos.features.length) {
|
|
1621
1654
|
console.error('[ERROR] ALL DUPLICATE');
|
|
1622
1655
|
}
|
|
1623
1656
|
const hexCategoryRemaining = Object.values(currentHexCategory.terms).reduce((sum, term) => sum + (term.remaining || 0), 0);
|
|
1624
1657
|
currentHexCategory.remaining = hexCategoryRemaining;
|
|
1625
|
-
currentHexCategory.total = hexCategoryRemaining + idSet.size;
|
|
1626
1658
|
currentHexCategory.crawled = idSet.size;
|
|
1659
|
+
const reportedExpected = Number(rtn.progressExpected ?? rtn.expected);
|
|
1660
|
+
if (Number.isFinite(reportedExpected) && reportedExpected > 0) {
|
|
1661
|
+
currentHexCategory.progressExpected = Math.max(currentHexCategory.progressExpected ?? 0, reportedExpected);
|
|
1662
|
+
}
|
|
1663
|
+
const fallbackTotal = hexCategoryRemaining + idSet.size;
|
|
1664
|
+
currentHexCategory.total = fallbackTotal;
|
|
1665
|
+
const categoryProgressCompleted = Object.values(currentHexCategory.terms)
|
|
1666
|
+
.reduce((sum, term) => sum + (term.progressCompleted ?? 0), 0);
|
|
1667
|
+
currentHexCategory.progressCompleted = categoryProgressCompleted > 0
|
|
1668
|
+
? categoryProgressCompleted
|
|
1669
|
+
: undefined;
|
|
1670
|
+
const progressBaseline = Number.isFinite(currentHexCategory.progressCompleted)
|
|
1671
|
+
? currentHexCategory.progressCompleted
|
|
1672
|
+
: currentHexCategory.crawled;
|
|
1673
|
+
if (currentHexCategory.progressExpected) {
|
|
1674
|
+
currentHexCategory.progressRemaining = Math.max(0, currentHexCategory.progressExpected - progressBaseline);
|
|
1675
|
+
} else {
|
|
1676
|
+
currentHexCategory.progressRemaining = undefined;
|
|
1677
|
+
}
|
|
1627
1678
|
const allTermsFinal = Object.values(currentHexCategory.terms).every(term => term.final);
|
|
1628
1679
|
currentHexCategory.final = allTermsFinal && hexCategoryRemaining === 0;
|
|
1680
|
+
if (currentHexCategory.final) {
|
|
1681
|
+
const completedValue = Number.isFinite(currentHexCategory.progressCompleted)
|
|
1682
|
+
? currentHexCategory.progressCompleted
|
|
1683
|
+
: currentHexCategory.crawled;
|
|
1684
|
+
currentHexCategory.progressCompleted = completedValue;
|
|
1685
|
+
currentHexCategory.progressExpected = completedValue;
|
|
1686
|
+
currentHexCategory.progressRemaining = 0;
|
|
1687
|
+
}
|
|
1629
1688
|
|
|
1630
1689
|
if (argv.debugVerbose) {
|
|
1631
1690
|
console.log('INFO:', ` ${rtn.hexId} ${rtn.category} ] dup=${duplicateCount}, out=${rtn.outside}, in=${rtn.photos.features.length} || ${currentHexCategory.crawled} / ${currentHexCategory.total}`);
|
package/package.json
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { mkdir } from 'node:fs/promises';
|
|
5
|
+
import Bottleneck from 'bottleneck';
|
|
5
6
|
import { ProviderBase } from '../../lib/ProviderBase.js';
|
|
6
7
|
import { bbox, polygon, centroid, booleanPointInPolygon, featureCollection } from '@turf/turf';
|
|
7
8
|
import { loadAPIKey } from '#lib/splatone';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_THROTTLE_MAX_CONCURRENT = 2;
|
|
11
|
+
const DEFAULT_THROTTLE_MIN_TIME_MS = 500;
|
|
8
12
|
export default class FlickrProvider extends ProviderBase {
|
|
9
13
|
|
|
10
14
|
static name = 'Flickr Provider'; // 任意
|
|
@@ -13,6 +17,8 @@ export default class FlickrProvider extends ProviderBase {
|
|
|
13
17
|
constructor(api, options = {}) {
|
|
14
18
|
super(api, options);
|
|
15
19
|
this.id = path.basename(path.dirname(fileURLToPath(import.meta.url)));//必須(ディレクトリ名がプロバイダ名)
|
|
20
|
+
this._throttleLimiter = null;
|
|
21
|
+
this._throttleKey = null;
|
|
16
22
|
}
|
|
17
23
|
async yargv(yargv) {
|
|
18
24
|
// 必須項目にすると、このプラグインを使用しない時も必須になります。
|
|
@@ -103,6 +109,16 @@ export default class FlickrProvider extends ProviderBase {
|
|
|
103
109
|
return Math.floor(num); // 確実に整数に
|
|
104
110
|
}
|
|
105
111
|
throw new Error(`Invalid date/time format: ${opt} (YYYY-MM-DD または UNIX時間(秒)で指定してください)`)
|
|
112
|
+
}).option(this.argKey('ThrottleMaxConcurrent'), {
|
|
113
|
+
group: 'For ' + this.id + ' Provider',
|
|
114
|
+
type: 'number',
|
|
115
|
+
default: DEFAULT_THROTTLE_MAX_CONCURRENT,
|
|
116
|
+
description: 'Flickr API リクエストの同時実行数'
|
|
117
|
+
}).option(this.argKey('ThrottleMinTimeMs'), {
|
|
118
|
+
group: 'For ' + this.id + ' Provider',
|
|
119
|
+
type: 'number',
|
|
120
|
+
default: DEFAULT_THROTTLE_MIN_TIME_MS,
|
|
121
|
+
description: '連続リクエスト間の最小待機時間 (ミリ秒)'
|
|
106
122
|
});
|
|
107
123
|
}
|
|
108
124
|
|
|
@@ -120,9 +136,28 @@ export default class FlickrProvider extends ProviderBase {
|
|
|
120
136
|
await mkdir(resolved, { recursive: true });
|
|
121
137
|
options['GimmeGimme'] = resolved;
|
|
122
138
|
}
|
|
139
|
+
const throttleMaxConcRaw = Number(options['ThrottleMaxConcurrent'] ?? DEFAULT_THROTTLE_MAX_CONCURRENT);
|
|
140
|
+
const throttleMinTimeRaw = Number(options['ThrottleMinTimeMs'] ?? DEFAULT_THROTTLE_MIN_TIME_MS);
|
|
141
|
+
options['ThrottleMaxConcurrent'] = Number.isFinite(throttleMaxConcRaw) && throttleMaxConcRaw > 0
|
|
142
|
+
? Math.floor(throttleMaxConcRaw)
|
|
143
|
+
: DEFAULT_THROTTLE_MAX_CONCURRENT;
|
|
144
|
+
options['ThrottleMinTimeMs'] = Number.isFinite(throttleMinTimeRaw) && throttleMinTimeRaw >= 0
|
|
145
|
+
? Math.floor(throttleMinTimeRaw)
|
|
146
|
+
: DEFAULT_THROTTLE_MIN_TIME_MS;
|
|
123
147
|
return options;
|
|
124
148
|
}
|
|
125
149
|
|
|
150
|
+
getThrottleLimiter({ ThrottleMaxConcurrent, ThrottleMinTimeMs } = {}) {
|
|
151
|
+
const maxConcurrent = Math.max(1, Math.floor(ThrottleMaxConcurrent ?? DEFAULT_THROTTLE_MAX_CONCURRENT));
|
|
152
|
+
const minTime = Math.max(0, Math.floor(ThrottleMinTimeMs ?? DEFAULT_THROTTLE_MIN_TIME_MS));
|
|
153
|
+
const key = `${maxConcurrent}:${minTime}`;
|
|
154
|
+
if (!this._throttleLimiter || this._throttleKey !== key) {
|
|
155
|
+
this._throttleLimiter = new Bottleneck({ maxConcurrent, minTime });
|
|
156
|
+
this._throttleKey = key;
|
|
157
|
+
}
|
|
158
|
+
return this._throttleLimiter;
|
|
159
|
+
}
|
|
160
|
+
|
|
126
161
|
async stop() {
|
|
127
162
|
//this.api.log(`[${this.constructor.id}] stop`);
|
|
128
163
|
}
|
|
@@ -142,6 +177,15 @@ export default class FlickrProvider extends ProviderBase {
|
|
|
142
177
|
return featureCollection(selected);
|
|
143
178
|
}
|
|
144
179
|
const hexQuery = {};
|
|
180
|
+
const throttleMaxConcurrent = providerOptions?.ThrottleMaxConcurrent ?? this.options?.ThrottleMaxConcurrent ?? DEFAULT_THROTTLE_MAX_CONCURRENT;
|
|
181
|
+
const throttleMinTimeMs = providerOptions?.ThrottleMinTimeMs ?? this.options?.ThrottleMinTimeMs ?? DEFAULT_THROTTLE_MIN_TIME_MS;
|
|
182
|
+
const limiter = this.getThrottleLimiter({ ThrottleMaxConcurrent: throttleMaxConcurrent, ThrottleMinTimeMs: throttleMinTimeMs });
|
|
183
|
+
const resolvedProviderOptions = {
|
|
184
|
+
...(this.options || {}),
|
|
185
|
+
...(providerOptions || {}),
|
|
186
|
+
ThrottleMaxConcurrent: throttleMaxConcurrent,
|
|
187
|
+
ThrottleMinTimeMs: throttleMinTimeMs
|
|
188
|
+
};
|
|
145
189
|
const ks = Object.keys(hexGrid.features);
|
|
146
190
|
ks.map(k => {
|
|
147
191
|
const item = hexGrid.features[k];
|
|
@@ -151,15 +195,18 @@ export default class FlickrProvider extends ProviderBase {
|
|
|
151
195
|
const tags = categories[ck];
|
|
152
196
|
//console.log("tag=",ck,"/",tags);
|
|
153
197
|
hexQuery[item.properties.hexId][ck] = { photos: [], tags, final: false };
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
198
|
+
limiter.schedule(() => {
|
|
199
|
+
this.api.emit('splatone:start', { //WorkerOptions
|
|
200
|
+
provider: this.id,
|
|
201
|
+
hex: item,
|
|
202
|
+
triangles: getTrianglesInHex(item, triangles),
|
|
203
|
+
bbox: bbox(item.geometry),
|
|
204
|
+
category: ck,
|
|
205
|
+
tags,
|
|
206
|
+
providerOptions: resolvedProviderOptions,
|
|
207
|
+
sessionId
|
|
208
|
+
});
|
|
209
|
+
return null;
|
|
163
210
|
});
|
|
164
211
|
});
|
|
165
212
|
});
|