splatone 0.0.13 → 0.0.15
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/CHANGELOG.md +26 -0
- package/README.md +217 -77
- package/assets/screenshot_florida_hex_majorityr.png +0 -0
- package/assets/screenshot_venice_simple.png +0 -0
- package/color.js +97 -0
- package/crawler.js +39 -17
- package/lib/paletteGenerator.js +0 -1
- package/package.json +3 -2
- package/visualizer/bulky/node.js +1 -1
- package/visualizer/majority-hex/node.js +214 -0
- package/visualizer/majority-hex/web.js +251 -0
- package/visualizer/marker-cluster/node.js +10 -1
- package/visualizer/marker-cluster/web.js +2 -2
- package/assets/screenshot_venice_bulky.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## Versions
|
|
4
4
|
|
|
5
|
+
### v0.0.11 → v0.0.12
|
|
6
|
+
|
|
7
|
+
* Bottleneckを導入しクエリ間隔を適正値に調整 (3 queries/ 3 sec.)
|
|
8
|
+
* 時間軸分割並列処理のデフォルト化
|
|
9
|
+
* 地理的分割に加えて大量の結果がある場所は時間軸でもクエリを分解する
|
|
10
|
+
* 無効にするときは```--no-p-flickr-Haste```を付与
|
|
11
|
+
|
|
12
|
+
### v0.0.10 → v0.0.11
|
|
13
|
+
|
|
14
|
+
* 時間軸として使用する日付を選択可能に (```--p-flickr-DateMode```)
|
|
15
|
+
* upload: Flickrにアップロードされたタイムスタンプを遡ってクローリング (デフォルト)
|
|
16
|
+
* taken: 写真の撮影日時を遡ってクローリング
|
|
17
|
+
* extrasを指定可能に (```--p-flickr-Extras```)
|
|
18
|
+
* https://www.flickr.com/services/api/explore/flickr.photos.search
|
|
19
|
+
* デフォルト値: ```date_upload,date_taken,owner_name,geo,url_s,tags```
|
|
20
|
+
* これらはコマンドライン引数での指定の有無に関わらず付与されます
|
|
21
|
+
* 自動指定時のHexGridの最小サイズを0.5kmに
|
|
22
|
+
* [Bug Fix] 時間軸並列機能のバグ修正
|
|
23
|
+
|
|
24
|
+
### v0.0.8 → v0.0.9 → v0.0.10
|
|
25
|
+
|
|
26
|
+
* 【重要】**APIキー**の指定方法が変わりました。
|
|
27
|
+
* ```--p-flickr-APIKEY```オプションを使います。
|
|
28
|
+
* クエリを時間方向でも分割し効率化しました。(使い方に変更はありません)
|
|
29
|
+
|
|
30
|
+
|
|
5
31
|
### v0.0.7 → v0.0.8
|
|
6
32
|
|
|
7
33
|
* 範囲指定とHexGridの表示・非表示ができるようになりました。
|
package/README.md
CHANGED
|
@@ -13,35 +13,14 @@ SNSのジオタグ付きポストをキーワードに基づいて収集する
|
|
|
13
13
|
|
|
14
14
|
## Change Log
|
|
15
15
|
|
|
16
|
+
### v0.0.13 → v0.0.14 → v0.0.15
|
|
17
|
+
* **[可視化モジュール]** ```--vis-majority-hex```追加
|
|
18
|
+
* 結果の色固定機能追加 (キーワード指定方法を参照の事)
|
|
19
|
+
|
|
16
20
|
### v0.0.12 → v0.0.13
|
|
17
21
|
* BulkyのPointMarkerのサイズや透明度を可変に
|
|
18
22
|
* コマンドライン引数で指定 (詳しくは``` npx -y -- splatone@latest crawler --help```)
|
|
19
23
|
|
|
20
|
-
### v0.0.11 → v0.0.12
|
|
21
|
-
|
|
22
|
-
* Bottleneckを導入しクエリ間隔を適正値に調整 (3 queries/ 3 sec.)
|
|
23
|
-
* 時間軸分割並列処理のデフォルト化
|
|
24
|
-
* 地理的分割に加えて大量の結果がある場所は時間軸でもクエリを分解する
|
|
25
|
-
* 無効にするときは```--no-p-flickr-Haste```を付与
|
|
26
|
-
|
|
27
|
-
### v0.0.10 → v0.0.11
|
|
28
|
-
|
|
29
|
-
* 時間軸として使用する日付を選択可能に (```--p-flickr-DateMode```)
|
|
30
|
-
* upload: Flickrにアップロードされたタイムスタンプを遡ってクローリング (デフォルト)
|
|
31
|
-
* taken: 写真の撮影日時を遡ってクローリング
|
|
32
|
-
* extrasを指定可能に (```--p-flickr-Extras```)
|
|
33
|
-
* https://www.flickr.com/services/api/explore/flickr.photos.search
|
|
34
|
-
* デフォルト値: ```date_upload,date_taken,owner_name,geo,url_s,tags```
|
|
35
|
-
* これらはコマンドライン引数での指定の有無に関わらず付与されます
|
|
36
|
-
* 自動指定時のHexGridの最小サイズを0.5kmに
|
|
37
|
-
* [Bug Fix] 時間軸並列機能のバグ修正
|
|
38
|
-
|
|
39
|
-
### v0.0.8 → v0.0.9 → v0.0.10
|
|
40
|
-
|
|
41
|
-
* 【重要】**APIキー**の指定方法が変わりました。
|
|
42
|
-
* ```--p-flickr-APIKEY```オプションを使います。
|
|
43
|
-
* クエリを時間方向でも分割し効率化しました。(使い方に変更はありません)
|
|
44
|
-
|
|
45
24
|
[これ以前のログ](CHANGELOG.md)
|
|
46
25
|
|
|
47
26
|
|
|
@@ -59,7 +38,6 @@ $ npx -y -- splatone@latest crawler --help
|
|
|
59
38
|
|
|
60
39
|
Basic Options
|
|
61
40
|
-p, --plugin 実行するプラグイン[文字列] [必須] [選択してください: "flickr"]
|
|
62
|
-
-o, --options プラグインオプション [文字列] [デフォルト: "{}"]
|
|
63
41
|
-k, --keywords 検索キーワード(|区切り) [文字列] [デフォルト:
|
|
64
42
|
"nature,tree,flower|building,house|water,sea,river,pond"]
|
|
65
43
|
-f, --filed 大きなデータをファイルとして送受信する
|
|
@@ -74,57 +52,150 @@ For flickr Plugin
|
|
|
74
52
|
--p-flickr-APIKEY Flickr ServiceのAPI KEY [文字列]
|
|
75
53
|
--p-flickr-Extras カンマ区切り/保持する写真のメタデータ(デフォルト値は
|
|
76
54
|
記載の有無に関わらず保持)
|
|
77
|
-
[文字列] [デフォルト: "date_upload,date_taken,owner_name,geo,url_s,tags"]
|
|
78
|
-
--p-flickr-DateMode 利用時間軸(update=Flickr投稿日時/taken=写真撮影日時)
|
|
79
|
-
[選択してください: "upload", "taken"] [デフォルト: "upload"]
|
|
80
|
-
--p-flickr-Haste 時間軸分割並列処理 [真偽] [デフォルト: true]
|
|
81
|
-
--p-flickr-DateMax クローリング期間(最大) UNIX TIMEもしくはYYYY-MM-DD
|
|
82
|
-
[文字列] [デフォルト:
|
|
83
|
-
--p-flickr-DateMin クローリング期間(最小) UNIX TIMEもしくはYYYY-MM-DD
|
|
84
|
-
[文字列] [デフォルト: 1072882800]
|
|
55
|
+
[文字列] [デフォルト: "date_upload,date_taken,owner_name,geo,url_s,tags"]
|
|
56
|
+
--p-flickr-DateMode 利用時間軸(update=Flickr投稿日時/taken=写真撮影日時)
|
|
57
|
+
[選択してください: "upload", "taken"] [デフォルト: "upload"]
|
|
58
|
+
--p-flickr-Haste 時間軸分割並列処理 [真偽] [デフォルト: true]
|
|
59
|
+
--p-flickr-DateMax クローリング期間(最大) UNIX TIMEもしくはYYYY-MM-DD
|
|
60
|
+
[文字列] [デフォルト: 1763107393]
|
|
61
|
+
--p-flickr-DateMin クローリング期間(最小) UNIX TIMEもしくはYYYY-MM-DD
|
|
62
|
+
[文字列] [デフォルト: 1072882800]
|
|
85
63
|
|
|
86
64
|
Visualization (最低一つの指定が必須です)
|
|
87
65
|
--vis-bulky 全データをCircleMarkerとして地図上に表示
|
|
88
|
-
[真偽] [デフォルト: false]
|
|
66
|
+
[真偽] [デフォルト: false]
|
|
67
|
+
--vis-majority-hex HexGrid内で最も出現頻度が高いカテゴリの色で彩色。Hex
|
|
68
|
+
apartiteモードで6分割パイチャート表示。透明度は全体
|
|
69
|
+
で正規化。 [真偽] [デフォルト: false]
|
|
89
70
|
--vis-marker-cluster マーカークラスターとして地図上に表示
|
|
90
|
-
[真偽] [デフォルト: false]
|
|
71
|
+
[真偽] [デフォルト: false]
|
|
91
72
|
|
|
92
73
|
For bulky Visualizer
|
|
93
|
-
--v-bulky-Radius Point Marker
|
|
94
|
-
--v-bulky-Stroke Point Markerの線の有無 [真偽] [デフォルト: true]
|
|
95
|
-
--v-bulky-Weight Point Markerの線の太さ [数値] [デフォルト: 1]
|
|
96
|
-
--v-bulky-Opacity Point Markerの線の透明度 [数値] [デフォルト: 1]
|
|
97
|
-
--v-bulky-Filling Point Markerの塗りの有無 [真偽] [デフォルト: true]
|
|
98
|
-
--v-bulky-FillOpacity Point Markerの塗りの透明度 [数値] [デフォルト: 0.5]
|
|
74
|
+
--v-bulky-Radius Point Markerの半径 [数値] [デフォルト: 5]
|
|
75
|
+
--v-bulky-Stroke Point Markerの線の有無 [真偽] [デフォルト: true]
|
|
76
|
+
--v-bulky-Weight Point Markerの線の太さ [数値] [デフォルト: 1]
|
|
77
|
+
--v-bulky-Opacity Point Markerの線の透明度 [数値] [デフォルト: 1]
|
|
78
|
+
--v-bulky-Filling Point Markerの塗りの有無 [真偽] [デフォルト: true]
|
|
79
|
+
--v-bulky-FillOpacity Point Markerの塗りの透明度 [数値] [デフォルト: 0.5]
|
|
80
|
+
|
|
81
|
+
For majority-hex Visualizer
|
|
82
|
+
--v-majority-hex-Hexapartite 中のカテゴリの頻度に応じて六角形を分割色彩
|
|
83
|
+
[真偽] [デフォルト: false]
|
|
84
|
+
--v-majority-hex-HexOpacity 六角形の線の透明度 [数値] [デフォルト: 1]
|
|
85
|
+
--v-majority-hex-HexWeight 六角形の線の太さ [数値] [デフォルト: 1]
|
|
86
|
+
--v-majority-hex-MaxOpacity 正規化後の最大塗り透明度
|
|
87
|
+
[数値] [デフォルト: 0.9]
|
|
88
|
+
--v-majority-hex-MinOpacity 正規化後の最小塗り透明度
|
|
89
|
+
[数値] [デフォルト: 0.5]
|
|
90
|
+
|
|
91
|
+
For marker-cluster Visualizer
|
|
92
|
+
--v-marker-cluster-MaxClusterRadius クラスタを構成する範囲(半径)
|
|
93
|
+
[数値] [デフォルト: 80]
|
|
99
94
|
|
|
100
95
|
オプション:
|
|
101
|
-
--help ヘルプを表示 [真偽]
|
|
102
|
-
--version バージョンを表示 [真偽]
|
|
96
|
+
--help ヘルプを表示 [真偽]
|
|
97
|
+
--version バージョンを表示 [真偽]
|
|
103
98
|
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
cold_@bimota-due MINGW64 /c/GitHub/Splatone (61-可視化メソッドmajorityhex)
|
|
100
|
+
$ node crawler.js --help
|
|
101
|
+
[app] [plugin] loaded: flickr@1.0.0
|
|
102
|
+
使い方: crawler.js [options]
|
|
103
|
+
|
|
104
|
+
Basic Options
|
|
105
|
+
-p, --plugin 実行するプラグイン[文字列] [必須] [選択してください: "flickr"]
|
|
106
|
+
-k, --keywords 検索キーワード(|区切り) [文字列] [デフォルト:
|
|
107
|
+
"nature,tree,flower|building,house|water,sea,river,pond"]
|
|
108
|
+
-f, --filed 大きなデータをファイルとして送受信する
|
|
109
|
+
[真偽] [デフォルト: true]
|
|
110
|
+
-c, --chopped 大きなデータを細分化して送受信する
|
|
111
|
+
[非推奨] [真偽] [デフォルト: false]
|
|
106
112
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
- ブラウザが立ち上がるので地図上でポリゴンあるいは矩形で領域選択し、実行ボタンを押すとクロールが開始されます。
|
|
110
|
-
- 指定した範囲を内包するHexGrid(六角形グリッド)が生成され、その内側のみが収集されます。
|
|
111
|
-
- 結果が表示された後、結果をGeoJSON形式でダウンロードできます。
|
|
113
|
+
Debug
|
|
114
|
+
--debug-verbose デバッグ情報出力 [真偽] [デフォルト: false]
|
|
112
115
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
For flickr Plugin
|
|
117
|
+
--p-flickr-APIKEY Flickr ServiceのAPI KEY [文字列]
|
|
118
|
+
--p-flickr-Extras カンマ区切り/保持する写真のメタデータ(デフォルト値は
|
|
119
|
+
記載の有無に関わらず保持)
|
|
120
|
+
[文字列] [デフォルト: "date_upload,date_taken,owner_name,geo,url_s,tags"]
|
|
121
|
+
--p-flickr-DateMode 利用時間軸(update=Flickr投稿日時/taken=写真撮影日時)
|
|
122
|
+
[選択してください: "upload", "taken"] [デフォルト: "upload"]
|
|
123
|
+
--p-flickr-Haste 時間軸分割並列処理 [真偽] [デフォルト: true]
|
|
124
|
+
--p-flickr-DateMax クローリング期間(最大) UNIX TIMEもしくはYYYY-MM-DD
|
|
125
|
+
[文字列] [デフォルト: 1763107399]
|
|
126
|
+
--p-flickr-DateMin クローリング期間(最小) UNIX TIMEもしくはYYYY-MM-DD
|
|
127
|
+
[文字列] [デフォルト: 1072882800]
|
|
128
|
+
|
|
129
|
+
Visualization (最低一つの指定が必須です)
|
|
130
|
+
--vis-bulky 全データをCircleMarkerとして地図上に表示
|
|
131
|
+
[真偽] [デフォルト: false]
|
|
132
|
+
--vis-majority-hex HexGrid内で最も出現頻度が高いカテゴリの色で彩色。Hex
|
|
133
|
+
apartiteモードで6分割パイチャート表示。透明度は全体
|
|
134
|
+
で正規化。 [真偽] [デフォルト: false]
|
|
135
|
+
--vis-marker-cluster マーカークラスターとして地図上に表示
|
|
136
|
+
[真偽] [デフォルト: false]
|
|
137
|
+
|
|
138
|
+
For bulky Visualizer
|
|
139
|
+
--v-bulky-Radius Point Markerの半径 [数値] [デフォルト: 5]
|
|
140
|
+
--v-bulky-Stroke Point Markerの線の有無 [真偽] [デフォルト: true]
|
|
141
|
+
--v-bulky-Weight Point Markerの線の太さ [数値] [デフォルト: 1]
|
|
142
|
+
--v-bulky-Opacity Point Markerの線の透明度 [数値] [デフォルト: 1]
|
|
143
|
+
--v-bulky-Filling Point Markerの塗りの有無 [真偽] [デフォルト: true]
|
|
144
|
+
--v-bulky-FillOpacity Point Markerの塗りの透明度 [数値] [デフォルト: 0.5]
|
|
145
|
+
|
|
146
|
+
For majority-hex Visualizer
|
|
147
|
+
--v-majority-hex-Hexapartite 中のカテゴリの頻度に応じて六角形を分割色彩
|
|
148
|
+
[真偽] [デフォルト: false]
|
|
149
|
+
--v-majority-hex-HexOpacity 六角形の線の透明度 [数値] [デフォルト: 1]
|
|
150
|
+
--v-majority-hex-HexWeight 六角形の線の太さ [数値] [デフォルト: 1]
|
|
151
|
+
--v-majority-hex-MaxOpacity 正規化後の最大塗り透明度
|
|
152
|
+
[数値] [デフォルト: 0.9]
|
|
153
|
+
--v-majority-hex-MinOpacity 正規化後の最小塗り透明度
|
|
154
|
+
[数値] [デフォルト: 0.5]
|
|
155
|
+
|
|
156
|
+
For marker-cluster Visualizer
|
|
157
|
+
--v-marker-cluster-MaxClusterRadius クラスタを構成する範囲(半径)
|
|
158
|
+
[数値] [デフォルト: 80]
|
|
159
|
+
|
|
160
|
+
オプション:
|
|
161
|
+
--help ヘルプを表示 [真偽]
|
|
162
|
+
--version バージョンを表示 [真偽]
|
|
116
163
|
```
|
|
117
|
-
- オプションの **--vis-bulky** を **--vis-marker-cluster** に変更する事でマーカークラスターで可視化できます。
|
|
118
164
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
165
|
+
## 最小コマンド例
|
|
166
|
+
|
|
167
|
+
1. *plugin*を一つ、*visualizer*を一つ以上指定し、複数のキーワードでクロールを開始します。
|
|
168
|
+
* plugin: flickr
|
|
169
|
+
* visualizer: bulky
|
|
170
|
+
* キーワード: canal,river|street,alley|bridge
|
|
171
|
+
1. コマンドを実行するとWebブラウザで地図表示されるので、地図上の任意の位置に矩形あるいはポリゴンを描く
|
|
172
|
+
* 例えばベネチア
|
|
173
|
+
2. Start Crawlingボタンをクリックしクローリング開始
|
|
174
|
+
|
|
175
|
+

|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
$ npx -y -- splatone@latest crawler -p flickr -k "canal,river|street,alley|bridge" --vis-bulky --p-flickr-APIKEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
122
179
|
```
|
|
123
|
-
- ベネチア等の水路のある町でやると面白いです
|
|
124
180
|
|
|
125
181
|
# 詳細説明
|
|
126
182
|
|
|
127
|
-
##
|
|
183
|
+
## Plugin (クローラー)
|
|
184
|
+
|
|
185
|
+
### Flickr: Flickrのジオタグ付き写真を取得するクローラー
|
|
186
|
+
|
|
187
|
+
#### コマンドライン引数
|
|
188
|
+
|
|
189
|
+
| オプション | 説明 | 型 | デフォルト |
|
|
190
|
+
| :------------------------ | :---------------------------------------------------------------------------- | :------------- | :----------- |
|
|
191
|
+
| ```--p-flickr-APIKEY``` | Flickr ServiceのAPI KEY | 文字列 | |
|
|
192
|
+
| ```--p-flickr-Extras``` | カンマ区切り/保持する写真のメタデータ(デフォルト値は記載の有無に関わらず保持) | 文字列 | date_upload |,date_taken,owner_name,geo,url_s,tags
|
|
193
|
+
| ```--p-flickr-DateMode``` | 利用時間軸(update=Flickr投稿日時/taken=写真撮影日時) | 選択: "upload" | "taken" |,"upload"
|
|
194
|
+
| ```--p-flickr-Haste``` | 時間軸分割並列処理 | 真偽 | true |
|
|
195
|
+
| ```--p-flickr-DateMax``` | クローリング期間(最大) UNIX TIMEもしくはYYYY-MM-DD | 文字列 | (動的)現時刻 |
|
|
196
|
+
| ```--p-flickr-DateMin``` | クローリング期間(最小) UNIX TIMEもしくはYYYY-MM-DD | 文字列 | 1072882800 |
|
|
197
|
+
|
|
198
|
+
#### Flickr APIキーの与え方
|
|
128
199
|
|
|
129
200
|
APIキーは以下の3種類の方法で与える事ができます
|
|
130
201
|
- ```--option```に含める
|
|
@@ -134,34 +205,74 @@ APIキーは以下の3種類の方法で与える事ができます
|
|
|
134
205
|
- 環境変数で渡す
|
|
135
206
|
- ```API_KEY_plugin```という環境変数に格納する
|
|
136
207
|
- コマンドに毎回含めなくて良くなる。
|
|
137
|
-
- **flickr**の場合は```API_KEY_flickr```になります。
|
|
138
|
-
- ```plugin```はプラグイン名(flickr等)に置き換えてください。
|
|
139
|
-
- 一時的な環境変数を定義する事も可能です。(bash等)
|
|
140
|
-
- ```API_KEY_flickr="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" node crawler.js -p flickr -k "sea,ocean|mountain,mount" --vis-bulky```
|
|
141
208
|
- ファイルで渡す(npxでは不可)
|
|
142
209
|
- ルートディレクトリに```.API_KEY.plugin```というファイルを作成し保存
|
|
143
210
|
- ```plugin```はプラグイン名(flickr等)に置き換えてください。
|
|
144
211
|
- **flickr**の場合は```.API_KEY.flickr```になります。
|
|
145
212
|
- optionや環境変数で与えるよりも優先されます。
|
|
146
213
|
|
|
147
|
-
## Visualizer (
|
|
214
|
+
## Visualizer (可視化モジュール)
|
|
148
215
|
|
|
149
216
|
### Bulky: 全ての点を地図上にポイントする
|
|
150
217
|
|
|
151
|
-

|
|
152
219
|
|
|
153
|
-
|
|
220
|
+
#### コマンド例
|
|
221
|
+
* クエリは海と山のキーワード検索。上記スクリーンショットは日本のデータ
|
|
154
222
|
```shell
|
|
155
|
-
$
|
|
223
|
+
$ npx -y -- splatone@latest crawler -p flickr -k "sea,ocean|mountain,mount" --vis-bulky--p-flickr-APIKEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
156
224
|
```
|
|
157
225
|
|
|
226
|
+
#### コマンドライン引数
|
|
227
|
+
|
|
228
|
+
| オプション | 説明 | 型 | デフォルト |
|
|
229
|
+
| :-------------------------- | :------------------------- | :--- | :--------- |
|
|
230
|
+
| ```--v-bulky-Radius``` | Point Markerの半径 | 数値 | 5 |
|
|
231
|
+
| ```--v-bulky-Stroke``` | Point Markerの線の有無 | 真偽 | true |
|
|
232
|
+
| ```--v-bulky-Weight``` | Point Markerの線の太さ | 数値 | 1 |
|
|
233
|
+
| ```--v-bulky-Opacity``` | Point Markerの線の透明度 | 数値 | 1 |
|
|
234
|
+
| ```--v-bulky-Filling``` | Point Markerの塗りの有無 | 真偽 | true |
|
|
235
|
+
| ```--v-bulky-FillOpacity``` | Point Markerの塗りの透明度 | 数値 | 0.5 |
|
|
236
|
+
|
|
237
|
+
|
|
158
238
|
### Marker Cluster: 高密度の地点はマーカーをまとめて表示する
|
|
159
|
-

|
|
160
240
|
|
|
241
|
+
#### コマンド例
|
|
161
242
|
* クエリは水域と通路・橋梁・ランドマークを色分けしたもの、上記スクリーンショットはベネチア付近のデータ
|
|
162
243
|
```shell
|
|
163
|
-
$
|
|
244
|
+
$ npx -y -- splatone@latest crawler -p flickr -k "水域=canal,channel,waterway,river,stream,watercourse,sea,ocean,gulf,bay,strait,lagoon,offshore|橋梁=bridge,overpass,flyover,aqueduct,trestle|通路=street,road,thoroughfare,roadway,avenue,boulevard,lane,alley,roadway,carriageway,highway,motorway|ランドマーク=church,sanctuary,chapel,cathedral,basilica,minster,abbey" --vis-marker-cluster --vis-bulky --p-flickr-APIKEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### コマンドライン引数
|
|
248
|
+
|
|
249
|
+
| オプション | 説明 | 型 | デフォルト |
|
|
250
|
+
| :---------------------------------------- | :--------------------------- | :--- | :--------- |
|
|
251
|
+
| ```--v-marker-cluster-MaxClusterRadius``` | クラスタを構成する範囲(半径) | 数値 | 80 |
|
|
252
|
+
|
|
253
|
+
### Majority Hex: Hexグリッド内の出現頻度に応じた彩色
|
|
254
|
+
|
|
255
|
+

|
|
256
|
+
|
|
257
|
+
#### コマンド例
|
|
258
|
+
* クエリは水域・緑地・交通・ランドマークを色分けしたもの。上記スクリーンショットはフロリダ半島全体
|
|
259
|
+
*
|
|
260
|
+
```shell
|
|
261
|
+
$ npx -y -- splatone@latest crawler -p flickr -k "水域=canal,channel,waterway,river,stream,watercourse,sea,ocean,gulf,bay,strait,lagoon,offshore|緑地=forest,woods,turf,lawn,jungle,trees,rainforest,grove,savanna,steppe|交通=bridge,overpass,flyover,aqueduct,trestle,street,road,thoroughfare,roadway,avenue,boulevard,lane,alley,roadway,carriageway,highway,motorway|ランドマーク=church,chapel,cathedral,basilica,minster,temple,shrine,neon,theater,statue,museum,sculpture,zoo,aquarium,observatory" --vis-majority-hex --v-majority-hex-Hexapartite --p-flickr-APIKEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
164
262
|
```
|
|
263
|
+
|
|
264
|
+
#### コマンドライン引数
|
|
265
|
+
|
|
266
|
+
| オプション | 説明 | 型 | デフォルト |
|
|
267
|
+
| :------------------------------------ | :----------------------------------------- | :--- | :--------- |
|
|
268
|
+
| ```--v-majority-hex-Hexapartite``` | 中のカテゴリの頻度に応じて六角形を分割色彩 | 真偽 | false |
|
|
269
|
+
| ```--v-majority-hex-HexOpacity=1``` | 六角形の線の透明度 | 数値 | 1 |
|
|
270
|
+
| ```--v-majority-hex-HexWeight=1``` | 六角形の線の太さ | 数値 | 1 |
|
|
271
|
+
| ```--v-majority-hex-MaxOpacity=0.9``` | 正規化後の最大塗り透明度 | 数値 | 0.9 |
|
|
272
|
+
| ```--v-majority-hex-MinOpacity=0.3``` | 正規化後の最小塗り透明度 | 数値 | 0.5 |
|
|
273
|
+
|
|
274
|
+
* ```--v-majority-hex-Hexapartite```を指定すると各Hexセルを六分割の荒いPie Chartとして中のカテゴリ頻度に応じて彩色します。
|
|
275
|
+
|
|
165
276
|
## キーワード指定方法
|
|
166
277
|
|
|
167
278
|
### 比較キーワードの指定
|
|
@@ -188,30 +299,59 @@ seaだけでは集められるポストが限定されるので、同様の意
|
|
|
188
299
|
-k "海域=sea,ocean|山岳=mountain,mount"
|
|
189
300
|
```
|
|
190
301
|
|
|
191
|
-
###
|
|
302
|
+
### カテゴリ毎の色指定
|
|
303
|
+
|
|
304
|
+
カテゴリの内容に合わせた色を指定したい場合はコマンドライン引数にて行えます。例えば海域を青に、山岳を緑にしたい場合は、カテゴリ名に続けて**#RRGGBB**で指定します。
|
|
192
305
|
|
|
193
|
-
```shell
|
|
194
|
-
$ node crawler.js -p flickr -k "sea,ocean|mountain,mount" --vis-bulky--p-flickr-APIKEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
195
306
|
```
|
|
196
|
-
|
|
307
|
+
-k "海域#037dfc=sea,ocean|山岳#7fc266=mountain,mount"
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
色を簡単に探すための小さなコマンドが付属しています。
|
|
311
|
+
|
|
312
|
+
#### 色セット生成ツール(color.js)の使い方
|
|
313
|
+
|
|
314
|
+
このリポジトリには、コマンドラインで色のセットを生成する小さなユーティリティ `color.js` が含まれています。用途は以下の通りです。
|
|
197
315
|
|
|
316
|
+
- 指定した数のカラーパレット(セット)を生成する
|
|
317
|
+
- ターミナル上で色サンプルを ANSI Truecolor で確認する
|
|
318
|
+
- プレーンなカンマ区切り HEX リストを出力して他ツールに渡す
|
|
319
|
+
|
|
320
|
+
- 使い方(6色のカラーパレットを2セット作りたい):
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
npx -y -- splatone@latest color <count> <sets>
|
|
324
|
+
# 例: 6色を3セット生成(ターミナルに色付きで表示)
|
|
325
|
+
npx -y -- splatone@latest color 6 3
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
- オプション:
|
|
329
|
+
|
|
330
|
+
- `--no-ansi` : ANSI カラーシーケンスを出力せず、プレーンなカンマ区切りの HEX を出力します(パイプやログ向け)。
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
npx -y -- splatone@latest color --no-ansi 6 3
|
|
334
|
+
```
|
|
198
335
|
|
|
199
336
|
## ダウンロード
|
|
200
337
|
|
|
201
338
|
### 画像のダウンロード
|
|
202
339
|
|
|
203
340
|
* 結果の地図を画像(PNG形式)としてダウンロードするには、画面右下のアイコンをクリックしてください。
|
|
341
|
+
* 注意: 画像には凡例が含まれません
|
|
204
342
|
|
|
205
|
-

|
|
206
344
|
|
|
207
345
|
### データのダウンロード
|
|
208
346
|
|
|
209
347
|
* クロール結果をデータとしてダウンロードしたい場合は凡例の下にあるエクスポートボタンをクリックしてください。
|
|
348
|
+
* 指定したビジュアライザ毎にFeature Collectionとして結果が格納されます。
|
|
349
|
+
* クローリングしたデータそのものが欲しい場合はBulky等、単純なビジュアライザを指定してください。
|
|
210
350
|
|
|
211
|
-

|
|
212
352
|
|
|
213
353
|
### 広範囲なデータ収集例
|
|
214
354
|
|
|
215
|
-
*
|
|
355
|
+
* クエリ数はおおよそ1 query/secに調整されますので、時間はかかりますが大量のデータを収集する事も可能です。
|
|
216
356
|
|
|
217
357
|

|
|
Binary file
|
|
Binary file
|
package/color.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import paletteGenerator from './lib/paletteGenerator.js';
|
|
4
|
+
|
|
5
|
+
// Usage: node color.js <count> <sets>
|
|
6
|
+
// Example: node color.js 6 3
|
|
7
|
+
// Outputs HTML lines, each line is one set: comma-separated spans where
|
|
8
|
+
// each span shows the hex string in that color (text color set to the color).
|
|
9
|
+
|
|
10
|
+
function usage() {
|
|
11
|
+
console.log('Usage: node color.js <count> <sets>');
|
|
12
|
+
console.log(' count: number of colors per set (default 8)');
|
|
13
|
+
console.log(' sets: number of sets to generate (default 1)');
|
|
14
|
+
console.log(' --no-ansi: disable ANSI color escapes and output plain comma-separated HEX values');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
let argv = process.argv.slice(2) || [];
|
|
19
|
+
if (argv.length === 0) {
|
|
20
|
+
usage();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const noAnsi = argv.indexOf('--no-ansi') !== -1;
|
|
25
|
+
argv = argv.filter(a => a !== '--no-ansi');
|
|
26
|
+
|
|
27
|
+
const count = parseInt(argv[0], 10) || 8;
|
|
28
|
+
const sets = parseInt(argv[1], 10) || 1;
|
|
29
|
+
|
|
30
|
+
for (let s = 0; s < sets; s++) {
|
|
31
|
+
// Try to generate a palette via paletteGenerator; if it fails, fallback to HSL per-set
|
|
32
|
+
let cols = null;
|
|
33
|
+
try {
|
|
34
|
+
if (typeof Math.random.seed === 'function') {
|
|
35
|
+
Math.random.seed(Date.now() + s);
|
|
36
|
+
}
|
|
37
|
+
cols = paletteGenerator.generate(
|
|
38
|
+
count,
|
|
39
|
+
function (color) {
|
|
40
|
+
var hcl = color.hcl();
|
|
41
|
+
return hcl[0] >= 0 && hcl[0] <= 360
|
|
42
|
+
&& hcl[1] >= 54.96 && hcl[1] <= 134
|
|
43
|
+
&& hcl[2] >= 19.14 && hcl[2] <= 90.23;
|
|
44
|
+
},
|
|
45
|
+
true,
|
|
46
|
+
50,
|
|
47
|
+
false,
|
|
48
|
+
'CMC'
|
|
49
|
+
);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
cols = null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function hslToHex(h, s, l) {
|
|
55
|
+
s = Math.max(0, Math.min(1, s));
|
|
56
|
+
l = Math.max(0, Math.min(1, l));
|
|
57
|
+
const a = s * Math.min(l, 1 - l);
|
|
58
|
+
function f(n) {
|
|
59
|
+
const k = (n + h / 30) % 12;
|
|
60
|
+
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
61
|
+
return Math.round(255 * color).toString(16).padStart(2, '0');
|
|
62
|
+
}
|
|
63
|
+
return `#${f(0)}${f(8)}${f(4)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const hexes = (cols ? cols.map(c => (typeof c.hex === 'function' ? c.hex() : String(c))) :
|
|
67
|
+
Array.from({ length: count }, (_, i) => {
|
|
68
|
+
const hue = Math.round(((i / count) * 360 + s * 37) % 360);
|
|
69
|
+
return hslToHex(hue, 0.65, 0.5);
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (noAnsi) {
|
|
74
|
+
console.log(hexes.join(','));
|
|
75
|
+
} else {
|
|
76
|
+
const esc = (r, g, b) => `\u001b[38;2;${r};${g};${b}m`;
|
|
77
|
+
const reset = '\u001b[0m';
|
|
78
|
+
function hexToRgb(hex) {
|
|
79
|
+
const h = hex.replace('#', '');
|
|
80
|
+
const bigint = parseInt(h, 16);
|
|
81
|
+
return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255];
|
|
82
|
+
}
|
|
83
|
+
const out = hexes.map(h => {
|
|
84
|
+
const rgb = hexToRgb(h);
|
|
85
|
+
return `${esc(...rgb)}${h}${reset}`;
|
|
86
|
+
}).join(',');
|
|
87
|
+
console.log(out);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
main().catch(err => {
|
|
93
|
+
console.error(err);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
|
package/crawler.js
CHANGED
|
@@ -30,6 +30,7 @@ import { hideBin } from 'yargs/helpers';
|
|
|
30
30
|
// -------------------------------
|
|
31
31
|
import { loadPlugins } from './lib/pluginLoader.js';
|
|
32
32
|
import paletteGenerator from './lib/paletteGenerator.js';
|
|
33
|
+
import chroma from 'chroma-js';
|
|
33
34
|
import { dfsObject, bboxSize, saveGeoJsonObjectAsStream, buildPluginsOptions, loadAPIKey, buildVisualizersOptions } from '#lib/splatone';
|
|
34
35
|
|
|
35
36
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -116,12 +117,13 @@ try {
|
|
|
116
117
|
express.static(resolve(VIZ_BASE, name, 'public'))(req, res, next);
|
|
117
118
|
});
|
|
118
119
|
// コマンド例
|
|
119
|
-
// node crawler.js -p flickr -o '{"flickr":{"API_KEY":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}' -k "商業=shop,souvenir,market,supermarket,pharmacy,store,department|食べ物=food,drink,restaurant,cafe,bar
|
|
120
|
-
// node crawler.js -p flickr -k "水域=canal,channel,waterway,river,stream,watercourse,sea,ocean,gulf,bay,strait,lagoon,offshore|橋梁=bridge,overpass,flyover,aqueduct,trestle|通路=street,road,thoroughfare,roadway,avenue,boulevard,lane,alley,roadway,carriageway,highway,motorway|ランドマーク=church,sanctuary,chapel,cathedral,basilica,minster,abbey,temple,shrine" --vis-bulky
|
|
120
|
+
// node crawler.js -p flickr -o '{"flickr":{"API_KEY":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}' -k "商業=shop,souvenir,market,supermarket,pharmacy,store,department|食べ物=food,drink,restaurant,cafe,bar|美術館=museum,art,exhibition,expo,sculpture,heritage|公園=park,garden,flower,green,pond,playground" --vis-bulky
|
|
121
121
|
|
|
122
|
-
// node crawler.js -p flickr -k "
|
|
122
|
+
// node crawler.js -p flickr -k "水域=canal,channel,waterway,river,stream,watercourse,sea,ocean,gulf,bay,strait,lagoon,offshore|緑地=forest,woods,turf,lawn,jungle,trees,rainforest,grove,savanna,steppe|交通=bridge,overpass,flyover,aqueduct,trestle,street,road,thoroughfare,roadway,avenue,boulevard,lane,alley,roadway,carriageway,highway,motorway|ランドマーク=church,chapel,cathedral,basilica,minster,temple,shrine,neon,theater,statue,museum,sculpture,zoo,aquarium,observatory" --vis-bulky
|
|
123
123
|
|
|
124
|
-
// node crawler.js -p flickr -k "
|
|
124
|
+
// node crawler.js -p flickr -k "水辺=sea,ocean,beach,river,delta,lake,coast,creek|緑地=forest,woods,turf,lawn,jungle,trees,rainforest,grove,savanna,steppe|砂漠=desert,dune,outback,barren,wasteland" --vis-bulky
|
|
125
|
+
|
|
126
|
+
// node crawler.js -p flickr -k "商業=shop,souvenir,market,supermarket,pharmacy,drugstore,store,department,kiosk,bazaar,bookstore,cinema,showroom|飲食=bakery,food,drink,restaurant,cafe,bar,beer,wine,whiskey|文化施設=museum,gallery,theater,concert,library,monument,exhibition,expo,sculpture,heritage|公園=park,garden,flower,green,pond,playground" --vis-bulky
|
|
125
127
|
|
|
126
128
|
let yargv = await yargs(hideBin(process.argv))
|
|
127
129
|
.strict() // 未定義オプションはエラー
|
|
@@ -134,13 +136,6 @@ try {
|
|
|
134
136
|
describe: '実行するプラグイン',
|
|
135
137
|
type: 'string'
|
|
136
138
|
})
|
|
137
|
-
.option('options', {
|
|
138
|
-
group: 'Basic Options',
|
|
139
|
-
alias: 'o',
|
|
140
|
-
default: '{}',
|
|
141
|
-
describe: 'プラグインオプション',
|
|
142
|
-
type: 'string'
|
|
143
|
-
})
|
|
144
139
|
.option('keywords', {
|
|
145
140
|
group: 'Basic Options',
|
|
146
141
|
alias: 'k',
|
|
@@ -173,10 +168,12 @@ try {
|
|
|
173
168
|
})
|
|
174
169
|
.version()
|
|
175
170
|
.coerce({
|
|
171
|
+
/*
|
|
176
172
|
options: ((name) => (v) => {
|
|
177
173
|
try { return JSON.parse(v); }
|
|
178
174
|
catch (e) { throw new Error(`--${name}: JSON エラー: ${e.message}`); }
|
|
179
175
|
})()
|
|
176
|
+
*/
|
|
180
177
|
});
|
|
181
178
|
plugins.list().forEach(async (plug) => {
|
|
182
179
|
yargv = await plugins.call(plug, "yargv", yargv);
|
|
@@ -384,12 +381,27 @@ try {
|
|
|
384
381
|
}
|
|
385
382
|
|
|
386
383
|
//カテゴリ生成
|
|
384
|
+
// キーにカラー指定が含まれている場合 (例: "水域#ff0000=canal,river") は
|
|
385
|
+
// カラー部分をキー名から取り除き、表示用の純粋なラベルをキーとして返す。
|
|
386
|
+
// 明示色があれば explicitColors に記録しておき、後でパレット生成時に利用する。
|
|
387
|
+
const explicitColors = {};
|
|
387
388
|
const categorize = (tags) => {
|
|
388
389
|
let cats = {};
|
|
389
390
|
tags.split('|').forEach((tag_set, i) => {
|
|
390
391
|
const key_val = tag_set.split("=", 2);
|
|
391
|
-
|
|
392
|
+
// key_raw は元のキー(色コードを含む可能性あり)
|
|
393
|
+
const key_raw = (key_val.length == 1) ? key_val[0].split(",")[0] : key_val[0];
|
|
392
394
|
const val = (key_val.length == 1) ? key_val[0] : key_val[1];
|
|
395
|
+
// カラー指定を抽出してキー名から除去
|
|
396
|
+
const hexMatch = String(key_raw).match(/#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/);
|
|
397
|
+
let key = key_raw;
|
|
398
|
+
if (hexMatch) {
|
|
399
|
+
const explicit = hexMatch[0];
|
|
400
|
+
// display label から色コードを除去してトリム
|
|
401
|
+
key = key_raw.replace(explicit, '').trim();
|
|
402
|
+
// store explicit color for this cleaned key
|
|
403
|
+
explicitColors[key] = explicit;
|
|
404
|
+
}
|
|
393
405
|
cats[key] = val;
|
|
394
406
|
});
|
|
395
407
|
return cats;
|
|
@@ -413,12 +425,22 @@ try {
|
|
|
413
425
|
// Sort colors by differenciation first
|
|
414
426
|
const palette = paletteGenerator.diffSort(colors, 'Default');
|
|
415
427
|
const splatonePalette = Object.fromEntries(Object.entries(categories).map(([k, v]) => {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
428
|
+
// explicitColors にあればその色を使う(キーは既に色コードを取り除いた表示名)
|
|
429
|
+
if (explicitColors.hasOwnProperty(k)) {
|
|
430
|
+
const explicit = explicitColors[k];
|
|
431
|
+
const colors = {
|
|
432
|
+
color: chroma(explicit).hex(),
|
|
433
|
+
darken: chroma(explicit).darken(2).hex(),
|
|
434
|
+
brighten: chroma(explicit).brighten(2).hex()
|
|
435
|
+
};
|
|
436
|
+
return [k, colors];
|
|
421
437
|
}
|
|
438
|
+
const color = palette.pop();
|
|
439
|
+
const colors = {
|
|
440
|
+
color: color.hex(),
|
|
441
|
+
darken: color.darken(2).hex(),
|
|
442
|
+
brighten: color.brighten(2).hex()
|
|
443
|
+
};
|
|
422
444
|
return [k, colors];
|
|
423
445
|
}));
|
|
424
446
|
|
package/lib/paletteGenerator.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "splatone",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "Multi-layer Composite Heatmap",
|
|
5
5
|
"homepage": "https://github.com/YokoyamaLab/Splatone#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"type": "module",
|
|
16
16
|
"main": "index.js",
|
|
17
17
|
"bin": {
|
|
18
|
-
"crawler": "./crawler.js"
|
|
18
|
+
"crawler": "./crawler.js",
|
|
19
|
+
"colors": "./color.js"
|
|
19
20
|
},
|
|
20
21
|
"imports": {
|
|
21
22
|
"#lib/*": "./lib/*.js"
|
package/visualizer/bulky/node.js
CHANGED
|
@@ -18,7 +18,7 @@ export default class BulkyVisualizer extends VisualizerBase {
|
|
|
18
18
|
return yargv.option(this.argKey('Radius'), {
|
|
19
19
|
group: 'For ' + this.id + ' Visualizer',
|
|
20
20
|
type: 'number',
|
|
21
|
-
description: 'Point Marker
|
|
21
|
+
description: 'Point Markerの半径',
|
|
22
22
|
default: 5
|
|
23
23
|
}).option(this.argKey('Stroke'), {
|
|
24
24
|
group: 'For ' + this.id + ' Visualizer',
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { VisualizerBase } from '../../lib/VisualizerBase.js';
|
|
4
|
+
import { featureCollection } from "@turf/turf";
|
|
5
|
+
|
|
6
|
+
export default class MajorityHex extends VisualizerBase {
|
|
7
|
+
static name = 'MajorityHex Visualizer';
|
|
8
|
+
static version = '0.0.2';
|
|
9
|
+
static description = "HexGrid内で最も出現頻度が高いカテゴリの色で彩色。Hexapartiteモードで6分割パイチャート表示。透明度は全体で正規化。";
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
this.id = path.basename(path.dirname(fileURLToPath(import.meta.url)));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async yargv(yargv) {
|
|
17
|
+
return yargv.option(this.argKey('Hexapartite'), {
|
|
18
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
19
|
+
type: 'boolean',
|
|
20
|
+
description: '中のカテゴリの頻度に応じて六角形を分割色彩',
|
|
21
|
+
default: false
|
|
22
|
+
}).option(this.argKey('HexOpacity'), {
|
|
23
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
24
|
+
type: 'number',
|
|
25
|
+
description: '六角形の線の透明度',
|
|
26
|
+
default: 1
|
|
27
|
+
}).option(this.argKey('HexWeight'), {
|
|
28
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
29
|
+
type: 'number',
|
|
30
|
+
description: '六角形の線の太さ',
|
|
31
|
+
default: 1
|
|
32
|
+
}).option(this.argKey('MaxOpacity'), {
|
|
33
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
34
|
+
type: 'number',
|
|
35
|
+
description: '正規化後の最大塗り透明度',
|
|
36
|
+
default: 0.9
|
|
37
|
+
}).option(this.argKey('MinOpacity'), {
|
|
38
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
39
|
+
type: 'number',
|
|
40
|
+
description: '正規化後の最小塗り透明度',
|
|
41
|
+
default: 0.5
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getFutureCollection(result, target, visOptions) {
|
|
46
|
+
const hexIndex = {};
|
|
47
|
+
|
|
48
|
+
if (target && target.hex && Array.isArray(target.hex.features)) {
|
|
49
|
+
for (const hexF of target.hex.features) {
|
|
50
|
+
const id = hexF.properties && hexF.properties.hexId;
|
|
51
|
+
if (id != null) hexIndex[id] = hexF;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Build index of triangles by triangleId
|
|
56
|
+
const triIndex = {};
|
|
57
|
+
if (target && target.triangles && Array.isArray(target.triangles.features)) {
|
|
58
|
+
for (const triF of target.triangles.features) {
|
|
59
|
+
const triId = triF.properties && triF.properties.triangleId;
|
|
60
|
+
if (triId != null) triIndex[triId] = triF;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const hexDataList = [];
|
|
65
|
+
let maxTotal = 0;
|
|
66
|
+
let globalMaxCategoryCount = 0; // Track max category count across entire grid
|
|
67
|
+
|
|
68
|
+
for (const hexId in result) {
|
|
69
|
+
const cats = result[hexId] || {};
|
|
70
|
+
let total = 0;
|
|
71
|
+
let maxCat = null;
|
|
72
|
+
let maxCount = -1;
|
|
73
|
+
|
|
74
|
+
for (const cat in cats) {
|
|
75
|
+
const count = (cats[cat]?.items?.features?.length) ?? 0;
|
|
76
|
+
total += count;
|
|
77
|
+
if (count > maxCount) {
|
|
78
|
+
maxCount = count;
|
|
79
|
+
maxCat = cat;
|
|
80
|
+
}
|
|
81
|
+
// Track global max
|
|
82
|
+
if (count > globalMaxCategoryCount) {
|
|
83
|
+
globalMaxCategoryCount = count;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (total > 0) {
|
|
88
|
+
hexDataList.push({ hexId, cats, maxCat, maxCount, total });
|
|
89
|
+
if (total > maxTotal) {
|
|
90
|
+
maxTotal = total;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const out = { hex: [], triangles: [] };
|
|
96
|
+
const maxOp = (visOptions && visOptions.MaxOpacity) ?? 0.8;
|
|
97
|
+
const minOp = (visOptions && visOptions.MinOpacity) ?? 0.1;
|
|
98
|
+
const opRange = maxOp - minOp;
|
|
99
|
+
|
|
100
|
+
for (const hexData of hexDataList) {
|
|
101
|
+
const hexFeature = hexIndex[hexData.hexId];
|
|
102
|
+
if (!hexFeature) continue;
|
|
103
|
+
|
|
104
|
+
const f = JSON.parse(JSON.stringify(hexFeature));
|
|
105
|
+
f.properties = f.properties || {};
|
|
106
|
+
f.properties.majorityCategory = hexData.maxCat;
|
|
107
|
+
f.properties.majorityCount = hexData.maxCount;
|
|
108
|
+
f.properties.totalCount = hexData.total;
|
|
109
|
+
|
|
110
|
+
const normalizedOpacity = maxTotal > 0
|
|
111
|
+
? minOp + (hexData.total / maxTotal) * opRange
|
|
112
|
+
: minOp;
|
|
113
|
+
|
|
114
|
+
const palette = target?.splatonePalette || {};
|
|
115
|
+
const pal = hexData.maxCat ? (palette[hexData.maxCat] || {}) : {};
|
|
116
|
+
f.properties.fill = true;
|
|
117
|
+
f.properties.fillColor = pal.color || '#888888';
|
|
118
|
+
f.properties.fillOpacity = normalizedOpacity;
|
|
119
|
+
f.properties.color = pal.darken || '#333333';
|
|
120
|
+
f.properties.weight = (visOptions && visOptions.HexWeight) ?? 1;
|
|
121
|
+
f.properties.opacity = (visOptions && visOptions.HexOpacity) ?? 1;
|
|
122
|
+
|
|
123
|
+
if (visOptions && visOptions.Hexapartite) {
|
|
124
|
+
f.properties.hexPartite = true;
|
|
125
|
+
f.properties.breakdown = Object.fromEntries(
|
|
126
|
+
Object.entries(hexData.cats).map(([c, v]) => [c, (v?.items?.features?.length) ?? 0])
|
|
127
|
+
);
|
|
128
|
+
f.properties.categoryColors = Object.fromEntries(
|
|
129
|
+
Object.entries(hexData.cats).map(([c]) => [c, palette[c]?.color || '#888888'])
|
|
130
|
+
);
|
|
131
|
+
// Store global max for opacity normalization in web.js fallback
|
|
132
|
+
f.properties.globalMaxCategoryCount = globalMaxCategoryCount;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
out.hex.push(f);
|
|
136
|
+
|
|
137
|
+
// Add triangles for Hexapartite mode
|
|
138
|
+
if (visOptions && visOptions.Hexapartite && hexFeature.properties && hexFeature.properties.triIds) {
|
|
139
|
+
const triIds = hexFeature.properties.triIds;
|
|
140
|
+
// Extract numeric counts from category objects
|
|
141
|
+
const catEntries = Object.entries(hexData.cats).map(([cat, catObj]) => [cat, catObj.total]).sort((a, b) => b[1] - a[1]);
|
|
142
|
+
const total = catEntries.reduce((sum, [_, count]) => sum + count, 0);
|
|
143
|
+
|
|
144
|
+
// Compute slices for each category: ratio < 1/6 => 0 slices, 1/6 <= ratio < 2/6 => 1 slice, etc.
|
|
145
|
+
// Also store in which order categories should be placed (clockwise from north)
|
|
146
|
+
const catSliceList = []; // [{category, count, sliceCount, opacity}, ...]
|
|
147
|
+
let totalSlices = 0;
|
|
148
|
+
|
|
149
|
+
// First pass: allocate slices using floor
|
|
150
|
+
for (const [category, count] of catEntries) {
|
|
151
|
+
const ratio = total > 0 ? count / total : 0;
|
|
152
|
+
const sliceCount = Math.floor(ratio * 6); // 0-6 slices
|
|
153
|
+
// Compute opacity based on GLOBAL max category count:
|
|
154
|
+
// MinOpacity + (count / globalMaxCategoryCount) * (MaxOpacity - MinOpacity)
|
|
155
|
+
const sliceOpacity = minOp + (count / Math.max(globalMaxCategoryCount, 1)) * opRange;
|
|
156
|
+
catSliceList.push({ category, count, sliceCount, opacity: sliceOpacity });
|
|
157
|
+
totalSlices += sliceCount;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Second pass: distribute remaining slices (6 - totalSlices) to categories with largest remainders
|
|
161
|
+
if (totalSlices < 6) {
|
|
162
|
+
const remainders = catEntries.map(([category, count], idx) => {
|
|
163
|
+
const ratio = total > 0 ? count / total : 0;
|
|
164
|
+
const remainder = (ratio * 6) - Math.floor(ratio * 6);
|
|
165
|
+
return { idx, remainder };
|
|
166
|
+
}).sort((a, b) => b.remainder - a.remainder);
|
|
167
|
+
|
|
168
|
+
let remaining = 6 - totalSlices;
|
|
169
|
+
for (let i = 0; i < remainders.length && remaining > 0; i++) {
|
|
170
|
+
catSliceList[remainders[i].idx].sliceCount++;
|
|
171
|
+
remaining--;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Arrange slices clockwise starting from top (triIdx 0 = north/top)
|
|
176
|
+
let triIdx = 0;
|
|
177
|
+
for (const catSlice of catSliceList) {
|
|
178
|
+
for (let slicePos = 0; slicePos < catSlice.sliceCount && triIdx < triIds.length; slicePos++) {
|
|
179
|
+
const triId = triIds[triIdx];
|
|
180
|
+
const triFeature = triIndex[triId];
|
|
181
|
+
if (!triFeature) {
|
|
182
|
+
triIdx++;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const triCopy = JSON.parse(JSON.stringify(triFeature));
|
|
187
|
+
triCopy.properties = triCopy.properties || {};
|
|
188
|
+
triCopy.properties.category = catSlice.category;
|
|
189
|
+
triCopy.properties.categoryCount = catSlice.count;
|
|
190
|
+
triCopy.properties.slicePosition = slicePos; // which slice of this category (0-indexed)
|
|
191
|
+
triCopy.properties.sliceCount = catSlice.sliceCount; // total slices for this category
|
|
192
|
+
triCopy.properties.parentHexId = hexData.hexId;
|
|
193
|
+
triCopy.properties.fill = true;
|
|
194
|
+
const catColor = palette[catSlice.category]?.color || '#888888';
|
|
195
|
+
triCopy.properties.fillColor = catColor;
|
|
196
|
+
triCopy.properties.fillOpacity = catSlice.opacity;
|
|
197
|
+
triCopy.properties.color = palette[catSlice.category]?.darken || '#333333';
|
|
198
|
+
triCopy.properties.weight = (visOptions && visOptions.HexWeight) ?? 1;
|
|
199
|
+
triCopy.properties.opacity = (visOptions && visOptions.HexOpacity) ?? 1;
|
|
200
|
+
|
|
201
|
+
out.triangles.push(triCopy);
|
|
202
|
+
triIdx++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const outputCollections = {};
|
|
209
|
+
for (const [k, v] of Object.entries(out)) {
|
|
210
|
+
outputCollections[k] = featureCollection(v);
|
|
211
|
+
}
|
|
212
|
+
return outputCollections;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
let booted = false;
|
|
2
|
+
// keep last received geojson so helper renderers can access triangles/outlines
|
|
3
|
+
let lastGeojson = null;
|
|
4
|
+
// keep last visOptions for opacity bounds
|
|
5
|
+
let lastVisOptions = {};
|
|
6
|
+
export default async function main(map, geojson, options = {}) {
|
|
7
|
+
if (booted) return;
|
|
8
|
+
booted = true;
|
|
9
|
+
const layers = {};
|
|
10
|
+
// stash geojson and visOptions for render helpers
|
|
11
|
+
lastGeojson = geojson;
|
|
12
|
+
lastVisOptions = (options && options.visOptions) || {};
|
|
13
|
+
|
|
14
|
+
// Render hex layer with Leaflet GeoJSON
|
|
15
|
+
if (geojson && geojson.hex) {
|
|
16
|
+
const isHexapartite = options.visOptions && options.visOptions.Hexapartite;
|
|
17
|
+
|
|
18
|
+
// If Hexapartite mode, render triangles directly
|
|
19
|
+
if (isHexapartite) {
|
|
20
|
+
// Create a group to hold triangles + optional outlines
|
|
21
|
+
const group = L.featureGroup();
|
|
22
|
+
|
|
23
|
+
if (geojson && geojson.triangles && geojson.triangles.features) {
|
|
24
|
+
const triLayer = L.geoJSON(geojson.triangles, {
|
|
25
|
+
style: (feature) => {
|
|
26
|
+
const fillColor = feature.properties?.fillColor || '#888888';
|
|
27
|
+
return {
|
|
28
|
+
fill: true,
|
|
29
|
+
fillColor: fillColor,
|
|
30
|
+
fillOpacity: feature.properties?.fillOpacity ?? 0.5,
|
|
31
|
+
color: feature.properties?.color || '#333333',
|
|
32
|
+
weight: feature.properties?.weight || 1,
|
|
33
|
+
opacity: feature.properties?.opacity ?? 1,
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
onEachFeature: (feature, layer) => {
|
|
37
|
+
const cat = feature.properties?.category || 'Unknown';
|
|
38
|
+
const count = feature.properties?.categoryCount ?? 0;
|
|
39
|
+
const popupText = `Category: ${cat}<br/>Count: ${count}`;
|
|
40
|
+
layer.bindPopup(popupText);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
triLayer.addTo(group);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// optionally add hex outlines so user can see hex borders
|
|
47
|
+
if (geojson && geojson.hex && geojson.hex.features) {
|
|
48
|
+
const outline = L.geoJSON(geojson.hex, {
|
|
49
|
+
style: (feature) => ({
|
|
50
|
+
color: feature.properties.color || '#333333',
|
|
51
|
+
weight: feature.properties.weight || 1,
|
|
52
|
+
opacity: feature.properties.opacity ?? 1,
|
|
53
|
+
fill: false
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
outline.addTo(group);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
group.addTo(map);
|
|
60
|
+
layers['[MajorityHex]'] = group;
|
|
61
|
+
} else {
|
|
62
|
+
// Standard solid color hex rendering
|
|
63
|
+
const hexLayer = renderHexLayer(map, geojson.hex);
|
|
64
|
+
if (hexLayer) layers['[MajorityHex]'] = hexLayer;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return layers;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Render standard hex layer (solid color)
|
|
73
|
+
*/
|
|
74
|
+
function renderHexLayer(map, hexFeatureCollection) {
|
|
75
|
+
const layer = L.geoJSON(hexFeatureCollection, {
|
|
76
|
+
style: (feature) => {
|
|
77
|
+
return {
|
|
78
|
+
color: feature.properties.color,
|
|
79
|
+
weight: feature.properties.weight,
|
|
80
|
+
opacity: feature.properties.opacity,
|
|
81
|
+
fill: feature.properties.fill,
|
|
82
|
+
fillColor: feature.properties.fillColor,
|
|
83
|
+
fillOpacity: feature.properties.fillOpacity
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
onEachFeature: (feature, layer) => {
|
|
87
|
+
const majorityCategory = feature.properties.majorityCategory;
|
|
88
|
+
const totalCount = feature.properties.totalCount;
|
|
89
|
+
const majorityCount = feature.properties.majorityCount;
|
|
90
|
+
const html = `<strong>${majorityCategory}</strong><br/>Data: ${majorityCount}/${totalCount}`;
|
|
91
|
+
layer.bindTooltip(html, { direction: 'top', opacity: 0.9 });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
layer.addTo(map);
|
|
95
|
+
return layer;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Render Hexapartite layer: 6-slice pie chart representation using pre-computed triangles
|
|
100
|
+
*/
|
|
101
|
+
function renderHexPartiteLayer(map, hexFeatureCollection) {
|
|
102
|
+
// Build a FeatureGroup containing pre-computed triangle slices (if available)
|
|
103
|
+
// plus optional hex outlines/tooltips. Do NOT add to map here; return the group so
|
|
104
|
+
// callers can add it to the map or layer control.
|
|
105
|
+
// Return a Leaflet layer (FeatureGroup). This avoids passing booleans to
|
|
106
|
+
// layer controls elsewhere. If precomputed triangles are not available
|
|
107
|
+
// (they are rendered in main when present), fall back to creating
|
|
108
|
+
// center-to-edge triangles per hex so the UI still shows a Hexapartite view.
|
|
109
|
+
const group = L.featureGroup();
|
|
110
|
+
|
|
111
|
+
if (!hexFeatureCollection || !Array.isArray(hexFeatureCollection.features)) {
|
|
112
|
+
return group;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const features = hexFeatureCollection.features;
|
|
116
|
+
|
|
117
|
+
for (const feature of features) {
|
|
118
|
+
const breakdown = feature.properties?.breakdown || {};
|
|
119
|
+
const categoryColors = feature.properties?.categoryColors || {};
|
|
120
|
+
const hexOutlineColor = feature.properties?.color || '#333333';
|
|
121
|
+
const hexOutlineWeight = feature.properties?.weight || 1;
|
|
122
|
+
const hexOutlineOpacity = feature.properties?.opacity ?? 1;
|
|
123
|
+
|
|
124
|
+
const coords = feature.geometry?.coordinates?.[0];
|
|
125
|
+
if (!coords || coords.length < 3) continue;
|
|
126
|
+
|
|
127
|
+
// normalize ring
|
|
128
|
+
const last = coords[coords.length - 1];
|
|
129
|
+
const first = coords[0];
|
|
130
|
+
const closed = last && first && last[0] === first[0] && last[1] === first[1];
|
|
131
|
+
const n = closed ? coords.length - 1 : coords.length;
|
|
132
|
+
|
|
133
|
+
// centroid
|
|
134
|
+
let sumLng = 0, sumLat = 0;
|
|
135
|
+
for (let i = 0; i < n; i++) {
|
|
136
|
+
sumLng += coords[i][0];
|
|
137
|
+
sumLat += coords[i][1];
|
|
138
|
+
}
|
|
139
|
+
const center = [sumLng / n, sumLat / n];
|
|
140
|
+
|
|
141
|
+
const catEntries = Object.entries(breakdown).sort((a, b) => b[1] - a[1]);
|
|
142
|
+
const total = catEntries.reduce((s, [, c]) => s + c, 0);
|
|
143
|
+
if (total === 0) continue;
|
|
144
|
+
|
|
145
|
+
// Get opacity bounds from lastVisOptions (same as node.js)
|
|
146
|
+
const minOp = (lastVisOptions && lastVisOptions.MinOpacity) ?? 0.1;
|
|
147
|
+
const maxOp = (lastVisOptions && lastVisOptions.MaxOpacity) ?? 0.8;
|
|
148
|
+
const opRange = maxOp - minOp;
|
|
149
|
+
|
|
150
|
+
// Get global max count from hex properties (set by node.js)
|
|
151
|
+
const globalMaxCount = feature.properties?.globalMaxCategoryCount ?? 0;
|
|
152
|
+
|
|
153
|
+
// Compute slice allocation per category: ratio < 1/6 => 0, 1/6 <= ratio < 2/6 => 1, etc.
|
|
154
|
+
const catSliceList = [];
|
|
155
|
+
let totalSlices = 0;
|
|
156
|
+
|
|
157
|
+
for (const [category, count] of catEntries) {
|
|
158
|
+
const ratio = total > 0 ? count / total : 0;
|
|
159
|
+
const sliceCount = Math.floor(ratio * 6);
|
|
160
|
+
if (sliceCount > 0) {
|
|
161
|
+
// Opacity based on GLOBAL max category count (same as node.js)
|
|
162
|
+
const sliceOpacity = minOp + (count / Math.max(globalMaxCount, 1)) * opRange;
|
|
163
|
+
catSliceList.push({ category, count, sliceCount, opacity: sliceOpacity });
|
|
164
|
+
totalSlices += sliceCount;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Normalize if totalSlices > 6
|
|
169
|
+
if (totalSlices > 6) {
|
|
170
|
+
const scale = 6 / totalSlices;
|
|
171
|
+
let allottedSlices = 0;
|
|
172
|
+
for (let i = 0; i < catSliceList.length; i++) {
|
|
173
|
+
const newCount = Math.floor(catSliceList[i].sliceCount * scale);
|
|
174
|
+
allottedSlices += newCount;
|
|
175
|
+
catSliceList[i].sliceCount = newCount;
|
|
176
|
+
}
|
|
177
|
+
let remaining = 6 - allottedSlices;
|
|
178
|
+
for (let i = 0; i < catSliceList.length && remaining > 0; i++) {
|
|
179
|
+
if (catSliceList[i].sliceCount < Math.ceil(catSliceList[i].count / total * 6)) {
|
|
180
|
+
catSliceList[i].sliceCount++;
|
|
181
|
+
remaining--;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Create triangles in clockwise order starting from north (triIdx 0)
|
|
187
|
+
let triIdx = 0;
|
|
188
|
+
for (const catSlice of catSliceList) {
|
|
189
|
+
for (let slicePos = 0; slicePos < catSlice.sliceCount && triIdx < n; slicePos++) {
|
|
190
|
+
const v1 = coords[triIdx];
|
|
191
|
+
const v2 = coords[(triIdx + 1) % n];
|
|
192
|
+
|
|
193
|
+
const triangle = {
|
|
194
|
+
type: 'Feature',
|
|
195
|
+
geometry: {
|
|
196
|
+
type: 'Polygon',
|
|
197
|
+
coordinates: [[[center[0], center[1]], [v1[0], v1[1]], [v2[0], v2[1]], [center[0], center[1]]]]
|
|
198
|
+
},
|
|
199
|
+
properties: {
|
|
200
|
+
category: catSlice.category,
|
|
201
|
+
categoryCount: catSlice.count,
|
|
202
|
+
slicePosition: slicePos,
|
|
203
|
+
sliceCount: catSlice.sliceCount,
|
|
204
|
+
fill: true,
|
|
205
|
+
fillColor: categoryColors[catSlice.category] || '#888888',
|
|
206
|
+
fillOpacity: catSlice.opacity,
|
|
207
|
+
color: hexOutlineColor,
|
|
208
|
+
weight: 0.5,
|
|
209
|
+
opacity: hexOutlineOpacity
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
L.geoJSON(triangle, {
|
|
214
|
+
style: (f) => ({
|
|
215
|
+
fill: true,
|
|
216
|
+
fillColor: f.properties.fillColor,
|
|
217
|
+
fillOpacity: f.properties.fillOpacity,
|
|
218
|
+
color: f.properties.color,
|
|
219
|
+
weight: f.properties.weight,
|
|
220
|
+
opacity: f.properties.opacity
|
|
221
|
+
})
|
|
222
|
+
}).addTo(group);
|
|
223
|
+
|
|
224
|
+
triIdx++;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// add outlines (non-filled) for all hexes so borders are visible
|
|
230
|
+
const outline = L.geoJSON(hexFeatureCollection, {
|
|
231
|
+
style: (feature) => ({
|
|
232
|
+
color: feature.properties.color || '#333333',
|
|
233
|
+
weight: feature.properties.weight || 1,
|
|
234
|
+
opacity: feature.properties.opacity ?? 1,
|
|
235
|
+
fill: false
|
|
236
|
+
}),
|
|
237
|
+
onEachFeature: (feature, layer) => {
|
|
238
|
+
const majorityCategory = feature.properties?.majorityCategory;
|
|
239
|
+
const totalCount = feature.properties?.totalCount;
|
|
240
|
+
const majorityCount = feature.properties?.majorityCount;
|
|
241
|
+
const html = `<strong>${majorityCategory}</strong><br/>Data: ${majorityCount}/${totalCount}`;
|
|
242
|
+
layer.bindTooltip(html, { direction: 'top', opacity: 0.9 });
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
outline.addTo(group);
|
|
246
|
+
|
|
247
|
+
group.addTo(map);
|
|
248
|
+
return group;
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
}
|
|
@@ -14,7 +14,16 @@ export default class MarkerClusterVisualizer extends VisualizerBase {
|
|
|
14
14
|
super();
|
|
15
15
|
this.id = path.basename(path.dirname(fileURLToPath(import.meta.url)));//必須(ディレクトリ名がビジュアライザ名)
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
async yargv(yargv) {
|
|
18
|
+
// 必須項目にすると、このプラグインを使用しない時も必須になります。
|
|
19
|
+
// 必須項目は作らず、もしプラグインを使う上での制約違反はinitで例外を投げてください。
|
|
20
|
+
return yargv.option(this.argKey('MaxClusterRadius'), {
|
|
21
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
22
|
+
type: 'number',
|
|
23
|
+
description: 'クラスタを構成する範囲(半径)',
|
|
24
|
+
default: 80
|
|
25
|
+
});
|
|
26
|
+
}
|
|
18
27
|
|
|
19
28
|
concatFC(fcA, fcB) {
|
|
20
29
|
return {
|
|
@@ -3,7 +3,7 @@ export default async function main(map, geojson, options = { palette: {}, visOpt
|
|
|
3
3
|
if (booted) return;
|
|
4
4
|
booted = true;
|
|
5
5
|
|
|
6
|
-
console.log("[VIS OPTIONS]",options.visOptions);
|
|
6
|
+
console.log("[VIS OPTIONS]", options.visOptions);
|
|
7
7
|
|
|
8
8
|
const urls = [
|
|
9
9
|
'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css',
|
|
@@ -35,7 +35,7 @@ console.log("[VIS OPTIONS]",options.visOptions);
|
|
|
35
35
|
const group = L.markerClusterGroup({
|
|
36
36
|
chunkedLoading: true,
|
|
37
37
|
disableClusteringAtZoom: 18,
|
|
38
|
-
maxClusterRadius:
|
|
38
|
+
maxClusterRadius: options.visOptions.MaxClusterRadius,
|
|
39
39
|
spiderfyOnMaxZoom: true,
|
|
40
40
|
showCoverageOnHover: false,
|
|
41
41
|
iconCreateFunction: (cluster) => {
|
|
Binary file
|