splatone 0.0.12 → 0.0.13
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 +34 -21
- package/crawler.js +23 -18
- package/lib/PluginBase.js +4 -1
- package/lib/VisualizerBase.js +13 -3
- package/lib/splatone.js +9 -2
- package/package.json +1 -1
- package/views/index.ejs +2 -1
- package/visualizer/bulky/node.js +47 -11
- package/visualizer/bulky/web.js +1 -0
- package/visualizer/marker-cluster/node.js +5 -1
- package/visualizer/marker-cluster/web.js +3 -1
package/README.md
CHANGED
|
@@ -13,6 +13,10 @@ SNSのジオタグ付きポストをキーワードに基づいて収集する
|
|
|
13
13
|
|
|
14
14
|
## Change Log
|
|
15
15
|
|
|
16
|
+
### v0.0.12 → v0.0.13
|
|
17
|
+
* BulkyのPointMarkerのサイズや透明度を可変に
|
|
18
|
+
* コマンドライン引数で指定 (詳しくは``` npx -y -- splatone@latest crawler --help```)
|
|
19
|
+
|
|
16
20
|
### v0.0.11 → v0.0.12
|
|
17
21
|
|
|
18
22
|
* Bottleneckを導入しクエリ間隔を適正値に調整 (3 queries/ 3 sec.)
|
|
@@ -54,40 +58,49 @@ $ npx -y -- splatone@latest crawler --help
|
|
|
54
58
|
使い方: crawler.js [options]
|
|
55
59
|
|
|
56
60
|
Basic Options
|
|
57
|
-
-p, --plugin 実行するプラグイン[文字列] [必須] [選択してください: "flickr"]
|
|
58
|
-
-o, --options プラグインオプション [文字列] [デフォルト: "{}"]
|
|
59
|
-
-k, --keywords 検索キーワード(|区切り) [文字列] [デフォルト:
|
|
60
|
-
"nature,tree,flower|building,house|water,sea,river,pond"]
|
|
61
|
+
-p, --plugin 実行するプラグイン[文字列] [必須] [選択してください: "flickr"]
|
|
62
|
+
-o, --options プラグインオプション [文字列] [デフォルト: "{}"]
|
|
63
|
+
-k, --keywords 検索キーワード(|区切り) [文字列] [デフォルト:
|
|
64
|
+
"nature,tree,flower|building,house|water,sea,river,pond"]
|
|
61
65
|
-f, --filed 大きなデータをファイルとして送受信する
|
|
62
|
-
[真偽] [デフォルト: true]
|
|
66
|
+
[真偽] [デフォルト: true]
|
|
63
67
|
-c, --chopped 大きなデータを細分化して送受信する
|
|
64
|
-
[非推奨] [真偽] [デフォルト: false]
|
|
68
|
+
[非推奨] [真偽] [デフォルト: false]
|
|
65
69
|
|
|
66
70
|
Debug
|
|
67
|
-
--debug-verbose デバッグ情報出力 [真偽] [デフォルト: false]
|
|
71
|
+
--debug-verbose デバッグ情報出力 [真偽] [デフォルト: false]
|
|
68
72
|
|
|
69
73
|
For flickr Plugin
|
|
70
|
-
--p-flickr-APIKEY Flickr ServiceのAPI KEY [文字列]
|
|
71
|
-
--p-flickr-Extras カンマ区切り/保持する写真のメタデータ(デフォルト値は
|
|
74
|
+
--p-flickr-APIKEY Flickr ServiceのAPI KEY [文字列]
|
|
75
|
+
--p-flickr-Extras カンマ区切り/保持する写真のメタデータ(デフォルト値は
|
|
72
76
|
記載の有無に関わらず保持)
|
|
73
|
-
[文字列] [デフォルト: "date_upload,date_taken,owner_name,geo,url_s,tags"]
|
|
74
|
-
--p-flickr-DateMode 利用時間軸(update=Flickr投稿日時/taken=写真撮影日時)
|
|
75
|
-
[選択してください: "upload", "taken"] [デフォルト: "upload"]
|
|
76
|
-
--p-flickr-Haste 時間軸分割並列処理 [真偽] [デフォルト: true]
|
|
77
|
-
--p-flickr-DateMax クローリング期間(最大) UNIX TIMEもしくはYYYY-MM-DD
|
|
78
|
-
[文字列] [デフォルト:
|
|
79
|
-
--p-flickr-DateMin クローリング期間(最小) UNIX TIMEもしくはYYYY-MM-DD
|
|
80
|
-
[文字列] [デフォルト: 1072882800]
|
|
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
|
+
[文字列] [デフォルト: 1762942310]
|
|
83
|
+
--p-flickr-DateMin クローリング期間(最小) UNIX TIMEもしくはYYYY-MM-DD
|
|
84
|
+
[文字列] [デフォルト: 1072882800]
|
|
81
85
|
|
|
82
86
|
Visualization (最低一つの指定が必須です)
|
|
83
87
|
--vis-bulky 全データをCircleMarkerとして地図上に表示
|
|
84
|
-
[真偽] [デフォルト: false]
|
|
88
|
+
[真偽] [デフォルト: false]
|
|
85
89
|
--vis-marker-cluster マーカークラスターとして地図上に表示
|
|
86
|
-
[真偽] [デフォルト: false]
|
|
90
|
+
[真偽] [デフォルト: false]
|
|
91
|
+
|
|
92
|
+
For bulky Visualizer
|
|
93
|
+
--v-bulky-Radius Point Markerの直径 [数値] [デフォルト: 5]
|
|
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]
|
|
87
99
|
|
|
88
100
|
オプション:
|
|
89
|
-
--help ヘルプを表示 [真偽]
|
|
90
|
-
--version バージョンを表示 [真偽]
|
|
101
|
+
--help ヘルプを表示 [真偽]
|
|
102
|
+
--version バージョンを表示 [真偽]
|
|
103
|
+
|
|
91
104
|
```
|
|
92
105
|
## クローリングの実行
|
|
93
106
|
|
package/crawler.js
CHANGED
|
@@ -30,7 +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 { dfsObject, bboxSize, saveGeoJsonObjectAsStream, buildPluginsOptions, loadAPIKey } from '#lib/splatone';
|
|
33
|
+
import { dfsObject, bboxSize, saveGeoJsonObjectAsStream, buildPluginsOptions, loadAPIKey, buildVisualizersOptions } from '#lib/splatone';
|
|
34
34
|
|
|
35
35
|
const __filename = fileURLToPath(import.meta.url);
|
|
36
36
|
const __dirname = dirname(__filename);
|
|
@@ -39,6 +39,8 @@ const app = express();
|
|
|
39
39
|
const port = 3000;
|
|
40
40
|
const title = 'Splatone - Multi-Layer Composite Heatmap Viewer';
|
|
41
41
|
let pluginsOptions = {};
|
|
42
|
+
let visOptions = {};
|
|
43
|
+
|
|
42
44
|
const flickrLimiter = new Bottleneck({
|
|
43
45
|
maxConcurrent: 5,
|
|
44
46
|
minTime: 350, // 約3req/sec
|
|
@@ -58,6 +60,7 @@ try {
|
|
|
58
60
|
api,
|
|
59
61
|
optionsById: {},
|
|
60
62
|
});
|
|
63
|
+
|
|
61
64
|
// Visualizer読み込み
|
|
62
65
|
const all_visualizers = {}; // { [name: string]: class }
|
|
63
66
|
// クラス判定の小ヘルパ
|
|
@@ -178,14 +181,20 @@ try {
|
|
|
178
181
|
plugins.list().forEach(async (plug) => {
|
|
179
182
|
yargv = await plugins.call(plug, "yargv", yargv);
|
|
180
183
|
})
|
|
181
|
-
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
const visualizers_ = {};
|
|
187
|
+
await Object.keys(all_visualizers).forEach(async (vis) => {
|
|
182
188
|
yargv = yargv.option('vis-' + vis, {
|
|
183
189
|
group: 'Visualization (最低一つの指定が必須です)',
|
|
184
190
|
type: 'boolean',
|
|
185
191
|
default: false,
|
|
186
192
|
description: all_visualizers[vis].description
|
|
187
193
|
})
|
|
194
|
+
visualizers_[vis] = new all_visualizers[vis]();
|
|
195
|
+
yargv = await visualizers_[vis].yargv(yargv);
|
|
188
196
|
});
|
|
197
|
+
|
|
189
198
|
yargv = yargv.check(async (argv, options) => {
|
|
190
199
|
if (Object.keys(all_visualizers).filter(v => argv["vis-" + v]).length == 0) {
|
|
191
200
|
throw new Error('可視化ツールの指定がありません。最低一つは指定してください。');
|
|
@@ -194,7 +203,9 @@ try {
|
|
|
194
203
|
console.warn("--filedと--choppedが両方指定されています。--filedが優先されます。");
|
|
195
204
|
argv.chopped = false;
|
|
196
205
|
}
|
|
197
|
-
pluginsOptions = buildPluginsOptions(argv, plugins.list())
|
|
206
|
+
pluginsOptions = buildPluginsOptions(argv, plugins.list());
|
|
207
|
+
visOptions = buildVisualizersOptions(argv, Object.keys(visualizers_));
|
|
208
|
+
//console.log(visOptions);
|
|
198
209
|
pluginsOptions[argv.plugin] = await plugins.call(argv.plugin, 'check', pluginsOptions[argv.plugin]);
|
|
199
210
|
return true;
|
|
200
211
|
});
|
|
@@ -202,20 +213,10 @@ try {
|
|
|
202
213
|
const argv = await yargv.parseAsync();
|
|
203
214
|
|
|
204
215
|
const visualizers = {};
|
|
205
|
-
for (const vis of Object.keys(
|
|
206
|
-
visualizers[vis] =
|
|
216
|
+
for (const vis of Object.keys(visualizers_).filter(v => argv[`vis-${v}`])) {
|
|
217
|
+
visualizers[vis] = visualizers_[vis];
|
|
207
218
|
}
|
|
208
219
|
|
|
209
|
-
/* const plugin_options = argv.options?.[argv.plugin] ?? {}
|
|
210
|
-
try {
|
|
211
|
-
plugin_options.API_KEY = await loadAPIKey("flickr") ?? plugin_options.API_KEY;
|
|
212
|
-
} catch (e) {
|
|
213
|
-
if (!plugin_options.API_KEY) {
|
|
214
|
-
console.error("Error loading API key:", e.message);
|
|
215
|
-
}
|
|
216
|
-
//Nothing to do
|
|
217
|
-
}*/
|
|
218
|
-
//pluginsOptions = buildPluginsOptions(argv, plugins.list());
|
|
219
220
|
await plugins.call(argv.plugin, 'init', pluginsOptions[argv.plugin]);
|
|
220
221
|
if (argv.debugVerbose) {
|
|
221
222
|
console.table([["Visualizer", Object.keys(visualizers)], ["Plugin", argv.plugin]]);
|
|
@@ -225,6 +226,7 @@ try {
|
|
|
225
226
|
const processing = {};
|
|
226
227
|
const crawlers = {};
|
|
227
228
|
const targets = {};
|
|
229
|
+
|
|
228
230
|
// 初期中心(凱旋門)
|
|
229
231
|
const DEFAULT_CENTER = { lat: 48.873611, lon: 2.294444 };
|
|
230
232
|
|
|
@@ -633,7 +635,8 @@ try {
|
|
|
633
635
|
const resultId = uniqid();
|
|
634
636
|
const result = crawlers[currentSessionId];
|
|
635
637
|
const target = targets[currentSessionId];
|
|
636
|
-
|
|
638
|
+
|
|
639
|
+
let geoJson = Object.fromEntries(Object.entries(visualizers).map(([vis, v]) => [vis, v.getFutureCollection(result, target, visOptions[vis])]));
|
|
637
640
|
|
|
638
641
|
//console.log('[splatone:finish]');
|
|
639
642
|
try {
|
|
@@ -645,7 +648,8 @@ try {
|
|
|
645
648
|
geoJson,
|
|
646
649
|
palette: target["splatonePalette"],
|
|
647
650
|
visualizers: Object.keys(visualizers),
|
|
648
|
-
plugin: argv.plugin
|
|
651
|
+
plugin: argv.plugin,
|
|
652
|
+
visOptions
|
|
649
653
|
});
|
|
650
654
|
} catch (e) {
|
|
651
655
|
if (e instanceof RangeError && /Invalid string length/.test(String(e.message))) {
|
|
@@ -719,7 +723,8 @@ try {
|
|
|
719
723
|
geoJson: null, /*geoJsonは送らない*/
|
|
720
724
|
palette: target["splatonePalette"],
|
|
721
725
|
visualizers: Object.keys(visualizers),
|
|
722
|
-
plugin: argv.plugin
|
|
726
|
+
plugin: argv.plugin,
|
|
727
|
+
visOptions
|
|
723
728
|
});
|
|
724
729
|
console.log("[Done]");
|
|
725
730
|
} else {
|
package/lib/PluginBase.js
CHANGED
|
@@ -16,9 +16,12 @@ export class PluginBase {
|
|
|
16
16
|
argKey(key) {
|
|
17
17
|
return "p-" + this.id + "-" + key;
|
|
18
18
|
}
|
|
19
|
+
async yargv(yargv) {
|
|
20
|
+
return yargv;
|
|
21
|
+
}
|
|
19
22
|
async check(option) {
|
|
20
23
|
//throw Error("Plugin Option Error!");
|
|
21
|
-
return
|
|
24
|
+
return options;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
async init(options = {}) {
|
package/lib/VisualizerBase.js
CHANGED
|
@@ -9,7 +9,17 @@ export class VisualizerBase {
|
|
|
9
9
|
/** @param {object} api - ホストが提供する能力(権限を最小化) */
|
|
10
10
|
constructor() {
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
argKey(key) {
|
|
13
|
+
return "v-" + this.id + "-" + key;
|
|
14
|
+
}
|
|
15
|
+
async yargv(yargv) {
|
|
16
|
+
return yargv;
|
|
17
|
+
}
|
|
18
|
+
async check(option) {
|
|
19
|
+
//throw Error("Plugin Option Error!");
|
|
20
|
+
return options;
|
|
21
|
+
}
|
|
22
|
+
async init() { }
|
|
23
|
+
async start() { }
|
|
24
|
+
async stop() { }
|
|
15
25
|
}
|
package/lib/splatone.js
CHANGED
|
@@ -160,10 +160,17 @@ export async function saveGeoJsonObjectAsStream(geoJsonObject, outfile) {
|
|
|
160
160
|
return destPath;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
export function buildVisualizersOptions(argv, visualizerIds) {
|
|
164
|
+
return buildOptions('v', argv, visualizerIds);
|
|
165
|
+
}
|
|
163
166
|
export function buildPluginsOptions(argv, pluginIds) {
|
|
167
|
+
return buildOptions('p', argv, pluginIds);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function buildOptions(context, argv, ids) {
|
|
164
171
|
const out = {};
|
|
165
|
-
for (const id of
|
|
166
|
-
const prefix =
|
|
172
|
+
for (const id of ids) {
|
|
173
|
+
const prefix = `${context}-${id}`;
|
|
167
174
|
const opts = {};
|
|
168
175
|
for (const [key, val] of Object.entries(argv)) {
|
|
169
176
|
if (key === '_' || key === '$0') continue;
|
package/package.json
CHANGED
package/views/index.ejs
CHANGED
|
@@ -337,7 +337,8 @@
|
|
|
337
337
|
};
|
|
338
338
|
for (const vis in visualizers) {
|
|
339
339
|
const layers = await visualizers[vis](map, res.geoJson[vis], {
|
|
340
|
-
palette: res.palette
|
|
340
|
+
palette: res.palette,
|
|
341
|
+
visOptions:res.visOptions[vis]
|
|
341
342
|
});
|
|
342
343
|
//console.log("【レイヤ】\n", JSON.stringify(layers,null,4));
|
|
343
344
|
if ((layers == null)) {
|
package/visualizer/bulky/node.js
CHANGED
|
@@ -1,18 +1,54 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
1
3
|
import { VisualizerBase } from '../../lib/VisualizerBase.js';
|
|
2
4
|
import { featureCollection } from "@turf/turf";
|
|
3
5
|
|
|
4
6
|
export default class BulkyVisualizer extends VisualizerBase {
|
|
5
|
-
static id = 'bulky'; // 一意ID(フォルダ名と一致させると運用しやすい)
|
|
6
7
|
static name = 'Bulky Visualizer'; // 表示名
|
|
7
8
|
static version = '0.0.0';
|
|
8
9
|
static description = "全データをCircleMarkerとして地図上に表示";
|
|
9
10
|
|
|
10
11
|
constructor() {
|
|
11
12
|
super();
|
|
13
|
+
this.id = path.basename(path.dirname(fileURLToPath(import.meta.url)));//必須(ディレクトリ名がビジュアライザ名)
|
|
14
|
+
}
|
|
15
|
+
async yargv(yargv) {
|
|
16
|
+
// 必須項目にすると、このプラグインを使用しない時も必須になります。
|
|
17
|
+
// 必須項目は作らず、もしプラグインを使う上での制約違反はinitで例外を投げてください。
|
|
18
|
+
return yargv.option(this.argKey('Radius'), {
|
|
19
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
20
|
+
type: 'number',
|
|
21
|
+
description: 'Point Markerの直径',
|
|
22
|
+
default: 5
|
|
23
|
+
}).option(this.argKey('Stroke'), {
|
|
24
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
description: 'Point Markerの線の有無',
|
|
27
|
+
default: true
|
|
28
|
+
}).option(this.argKey('Weight'), {
|
|
29
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
30
|
+
type: 'number',
|
|
31
|
+
description: 'Point Markerの線の太さ',
|
|
32
|
+
default: 1
|
|
33
|
+
}).option(this.argKey('Opacity'), {
|
|
34
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
35
|
+
type: 'number',
|
|
36
|
+
description: 'Point Markerの線の透明度',
|
|
37
|
+
default: 1
|
|
38
|
+
}).option(this.argKey('Filling'), {
|
|
39
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
description: 'Point Markerの塗りの有無',
|
|
42
|
+
default: true
|
|
43
|
+
}).option(this.argKey('FillOpacity'), {
|
|
44
|
+
group: 'For ' + this.id + ' Visualizer',
|
|
45
|
+
type: 'number',
|
|
46
|
+
description: 'Point Markerの塗りの透明度',
|
|
47
|
+
default: .5
|
|
48
|
+
});
|
|
12
49
|
}
|
|
13
50
|
|
|
14
|
-
getFutureCollection(result, target){
|
|
15
|
-
//console.log(JSON.stringify(target, null, 4));
|
|
51
|
+
getFutureCollection(result, target, visOptions) {
|
|
16
52
|
const layers = {};
|
|
17
53
|
for (const hex in result) {
|
|
18
54
|
for (const cat in result[hex]) {
|
|
@@ -20,17 +56,17 @@ export default class BulkyVisualizer extends VisualizerBase {
|
|
|
20
56
|
layers[cat] = [];
|
|
21
57
|
}
|
|
22
58
|
for (const feature of result[hex][cat].items.features) {
|
|
23
|
-
feature.properties["radius"] =
|
|
59
|
+
feature.properties["radius"] = visOptions.Radius;
|
|
24
60
|
|
|
25
|
-
feature.properties["stroke"] =
|
|
26
|
-
feature.properties["color"] = target.splatonePalette[cat].darken;
|
|
27
|
-
feature.properties["weight"] =
|
|
28
|
-
feature.properties["opacity"] =
|
|
61
|
+
feature.properties["stroke"] = visOptions.Stroke;
|
|
62
|
+
feature.properties["color"] = target.splatonePalette[cat].darken;
|
|
63
|
+
feature.properties["weight"] = visOptions.Weight;
|
|
64
|
+
feature.properties["opacity"] = visOptions.Opacity;
|
|
29
65
|
|
|
30
|
-
feature.properties["fill"] =
|
|
66
|
+
feature.properties["fill"] = visOptions.Filling;
|
|
31
67
|
feature.properties["fillColor"] = target.splatonePalette[cat].color;
|
|
32
|
-
feature.properties["fillOpacity"] = .
|
|
33
|
-
|
|
68
|
+
feature.properties["fillOpacity"] = visOptions.FillOpacity;
|
|
69
|
+
|
|
34
70
|
layers[cat].push(feature);
|
|
35
71
|
}
|
|
36
72
|
}
|
package/visualizer/bulky/web.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
1
3
|
import { VisualizerBase } from '../../lib/VisualizerBase.js';
|
|
2
4
|
import { featureCollection } from "@turf/turf";
|
|
3
5
|
|
|
@@ -10,8 +12,10 @@ export default class MarkerClusterVisualizer extends VisualizerBase {
|
|
|
10
12
|
|
|
11
13
|
constructor() {
|
|
12
14
|
super();
|
|
15
|
+
this.id = path.basename(path.dirname(fileURLToPath(import.meta.url)));//必須(ディレクトリ名がビジュアライザ名)
|
|
13
16
|
}
|
|
14
17
|
|
|
18
|
+
|
|
15
19
|
concatFC(fcA, fcB) {
|
|
16
20
|
return {
|
|
17
21
|
type: "FeatureCollection",
|
|
@@ -31,7 +35,7 @@ export default class MarkerClusterVisualizer extends VisualizerBase {
|
|
|
31
35
|
});
|
|
32
36
|
return { type: "FeatureCollection", features };
|
|
33
37
|
}
|
|
34
|
-
getFutureCollection(result, target) {
|
|
38
|
+
getFutureCollection(result, target, visOptions) {
|
|
35
39
|
//console.log(JSON.stringify(target, null, 4));
|
|
36
40
|
const layers = {};
|
|
37
41
|
for (const hex in result) {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
let booted = false;
|
|
2
|
-
export default async function main(map, geojson, options = { palette: {} }) {
|
|
2
|
+
export default async function main(map, geojson, options = { palette: {}, visOptions: {} }) {
|
|
3
3
|
if (booted) return;
|
|
4
4
|
booted = true;
|
|
5
5
|
|
|
6
|
+
console.log("[VIS OPTIONS]",options.visOptions);
|
|
7
|
+
|
|
6
8
|
const urls = [
|
|
7
9
|
'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css',
|
|
8
10
|
'https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css',
|