waibu-maps 1.0.1

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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/bajo/.alias +1 -0
  4. package/bajo/config.json +10 -0
  5. package/bajoI18N/resource/en-US.json +17 -0
  6. package/bajoI18N/resource/id.json +19 -0
  7. package/package.json +34 -0
  8. package/waibuBootstrap/theme/component/factory/control-attribution.js +29 -0
  9. package/waibuBootstrap/theme/component/factory/control-buttons-item.js +14 -0
  10. package/waibuBootstrap/theme/component/factory/control-buttons.js +67 -0
  11. package/waibuBootstrap/theme/component/factory/control-center-position.js +28 -0
  12. package/waibuBootstrap/theme/component/factory/control-draw.js +43 -0
  13. package/waibuBootstrap/theme/component/factory/control-fullscreen.js +26 -0
  14. package/waibuBootstrap/theme/component/factory/control-geolocate.js +27 -0
  15. package/waibuBootstrap/theme/component/factory/control-group-button.js +15 -0
  16. package/waibuBootstrap/theme/component/factory/control-group-menu.js +45 -0
  17. package/waibuBootstrap/theme/component/factory/control-group.js +94 -0
  18. package/waibuBootstrap/theme/component/factory/control-image.js +43 -0
  19. package/waibuBootstrap/theme/component/factory/control-loader.js +27 -0
  20. package/waibuBootstrap/theme/component/factory/control-logo.js +50 -0
  21. package/waibuBootstrap/theme/component/factory/control-mouse-pos.js +78 -0
  22. package/waibuBootstrap/theme/component/factory/control-navigation.js +29 -0
  23. package/waibuBootstrap/theme/component/factory/control-ruler.js +47 -0
  24. package/waibuBootstrap/theme/component/factory/control-scale.js +27 -0
  25. package/waibuBootstrap/theme/component/factory/control-search.js +159 -0
  26. package/waibuBootstrap/theme/component/factory/control-zbp.js +70 -0
  27. package/waibuBootstrap/theme/component/factory/control.js +42 -0
  28. package/waibuBootstrap/theme/component/factory/layer-geojson.js +103 -0
  29. package/waibuBootstrap/theme/component/factory/layer-html-cluster.js +124 -0
  30. package/waibuBootstrap/theme/component/factory/map/options.js +44 -0
  31. package/waibuBootstrap/theme/component/factory/map.js +139 -0
  32. package/waibuBootstrap/theme/component/factory/script.js +20 -0
  33. package/waibuBootstrap/theme/component/factory/template.js +10 -0
  34. package/waibuBootstrap/theme/component/wmaps-base.js +40 -0
  35. package/waibuMpa/partial/menu.html +10 -0
  36. package/waibuMpa/route/default-style.js +13 -0
  37. package/waibuMpa/route/wmaps.js +13 -0
  38. package/waibuMpa/template/default-style.json +20 -0
  39. package/waibuMpa/template/wmaps.js +320 -0
  40. package/waibuStatic/asset/css/control-buttons.css +3 -0
  41. package/waibuStatic/asset/css/control-center-position.css +17 -0
  42. package/waibuStatic/asset/css/control-loader.css +7 -0
  43. package/waibuStatic/asset/css/control-mouse-position.css +21 -0
  44. package/waibuStatic/asset/css/control-ruler.css +12 -0
  45. package/waibuStatic/asset/css/control-search.css +25 -0
  46. package/waibuStatic/asset/css/wmaps.css +150 -0
  47. package/waibuStatic/asset/font/noto_sans_regular.pbf +0 -0
  48. package/waibuStatic/asset/image/center.svg +25 -0
  49. package/waibuStatic/asset/image/ruler.svg +4 -0
  50. package/waibuStatic/asset/js/control-buttons.js +98 -0
  51. package/waibuStatic/asset/js/control-center-position.js +19 -0
  52. package/waibuStatic/asset/js/control-mouse-position.js +47 -0
  53. package/waibuStatic/asset/js/control-ruler.js +204 -0
  54. package/waibuStatic/asset/js/donut-chart.js +58 -0
  55. package/waibuStatic/asset/js/lib/worker-timers.js +243 -0
  56. package/waibuStatic/virtual.json +7 -0
@@ -0,0 +1,78 @@
1
+ import control from './control.js'
2
+
3
+ const prefix = 'cmp'
4
+
5
+ async function controlMousePos () {
6
+ const WmapsControl = await control.call(this)
7
+
8
+ return class WmapsControlMousePos extends WmapsControl {
9
+ constructor (options) {
10
+ super(options)
11
+ this.params.noTag = true
12
+ }
13
+
14
+ async build () {
15
+ const { generateId } = this.plugin.app.bajo
16
+ const { jsonStringify, minify } = this.plugin.app.waibuMpa
17
+ const { has, omit } = this.plugin.app.bajo.lib._
18
+ const options = omit(this.params.attr, ['octag', 'class', 'style', 'content'])
19
+ options.class = prefix + ' maplibregl-ctrl-group widget'
20
+ const id = generateId('alpha')
21
+ const tpl = await minify(await this.component.buildSentence(`
22
+ <c:grid-row>
23
+ <c:grid-col col="6">{%= lng %}</c:grid-col>
24
+ <c:grid-col col="6">{%= lat %}</c:grid-col>
25
+ </c:grid-row>
26
+ <c:grid-row style="font-size:smaller">
27
+ <c:grid-col col="6" t:content="Longitude" />
28
+ <c:grid-col col="6" t:content="Latitude" />
29
+ </c:grid-row>
30
+ `))
31
+ this.block.dataInit.push(`
32
+ this.$watch('$store.map.center', val => {
33
+ if (!this.${prefix}TrackCenter) return
34
+ this.${prefix}Pos = [...val]
35
+ })
36
+ this.$watch('${prefix}Pos', this.${prefix}Update.bind(this))
37
+ `)
38
+ this.block.reactive.push(`
39
+ ${prefix}Tpl: _.template('${tpl}')
40
+ `, `
41
+ ${prefix}Pos: [0, 0]
42
+ `, `
43
+ ${prefix}TrackCenter: ${has(this.params.attr, 'trackCenter') ? 'true' : 'false'}
44
+ `, `
45
+ ${prefix}Update () {
46
+ const el = document.querySelector('#${id}')
47
+ if (!el) return
48
+ const [lng, lat] = this.${prefix}Pos
49
+ el.innerHTML = this.${prefix}Tpl({
50
+ lng: wmpa.format(lng, 'float', { longitude: true }),
51
+ lat: wmpa.format(lat, 'float', { latitude: true })
52
+ })
53
+ }
54
+ `, `
55
+ async ${prefix}Builder () {
56
+ const body = '<c:div id="${id}" margin="x-2 top-1" text="align:center nowrap"/>'
57
+ return await wmpa.createComponent(body)
58
+ }
59
+ `)
60
+ this.block.run.push(`
61
+ map.on('mousemove', evt => {
62
+ if (this.${prefix}TrackCenter) return
63
+ const coord = evt.lngLat.wrap()
64
+ this.${prefix}Pos = [coord.lng, coord.lat]
65
+ })
66
+ if (this.${prefix}TrackCenter) this.${prefix}Pos = [...this.$store.map.center]
67
+ `)
68
+
69
+ this.block.control.push(`
70
+ await wmaps.createControl(_.merge(${jsonStringify(options, true)}, { builder: this.${prefix}Builder, firstCall: this.${prefix}Update }))
71
+ `)
72
+
73
+ this.params.html = this.writeBlock()
74
+ }
75
+ }
76
+ }
77
+
78
+ export default controlMousePos
@@ -0,0 +1,29 @@
1
+ import control from './control.js'
2
+
3
+ async function controlNavigation () {
4
+ const WmapsControl = await control.call(this)
5
+
6
+ return class WmapsControlNavigation extends WmapsControl {
7
+ constructor (options) {
8
+ super(options)
9
+ this.params.noTag = true
10
+ }
11
+
12
+ async build () {
13
+ const { jsonStringify } = this.plugin.app.waibuMpa
14
+ const opts = { showCompass: false }
15
+ if (this.params.attr.compass) opts.showCompass = true
16
+ if (this.params.attr.noZoom) opts.showZoom = false
17
+ if (this.params.attr.visualizePitch) opts.visualizePitch = true
18
+ opts.classSelector = 'maplibregl-ctrl-zoom-in'
19
+ opts.classGroup = true
20
+ opts.position = this.ctrlPos.includes(this.params.attr.position) ? this.params.attr.position : 'bottom-right'
21
+ this.block.control.push(`
22
+ await wmaps.createControlNative('NavigationControl', ${jsonStringify(opts, true)})
23
+ `)
24
+ this.params.html = this.writeBlock()
25
+ }
26
+ }
27
+ }
28
+
29
+ export default controlNavigation
@@ -0,0 +1,47 @@
1
+ import control from './control.js'
2
+ const storeKey = 'mapControl.ruler'
3
+
4
+ async function controlRuler () {
5
+ const WmapsControl = await control.call(this)
6
+
7
+ return class WmapsControlRuler extends WmapsControl {
8
+ static scripts = [
9
+ ...super.scripts,
10
+ 'bajoSpatial.virtual:/geolib/lib/index.js',
11
+ 'waibuMaps.asset:/js/control-ruler.js'
12
+ ]
13
+
14
+ static css = [
15
+ ...super.css,
16
+ 'waibuMaps.asset:/css/control-ruler.css'
17
+ ]
18
+
19
+ constructor (options) {
20
+ super(options)
21
+ this.params.noTag = true
22
+ }
23
+
24
+ async build () {
25
+ const { routePath } = this.plugin.app.waibu
26
+ const { jsonStringify } = this.plugin.app.waibuMpa
27
+ const pos = this.ctrlPos.includes(this.params.attr.position) ? this.params.attr.position : 'bottom-right'
28
+ const opts = {
29
+ imageUrl: routePath('waibuMaps.asset:/image/ruler.svg')
30
+ }
31
+ this.block.control.push(`
32
+ const rulerCtrl = new ControlRuler(${jsonStringify(opts, true)})
33
+ map.addControl(rulerCtrl${pos ? `, '${pos}'` : ''})
34
+ if (this.$store.mapControl) {
35
+ el = document.querySelector('#' + map._container.id + ' .maplibregl-ctrl-ruler')
36
+ el.setAttribute('x-data', '')
37
+ el.setAttribute('x-show', '$store.${storeKey}')
38
+ // TODO: disable measuring first
39
+ // el.setAttribute('x-init', "$watch('$store.${storeKey}', val => console.log(rulerCtrl))")
40
+ }
41
+ `)
42
+ this.params.html = this.writeBlock()
43
+ }
44
+ }
45
+ }
46
+
47
+ export default controlRuler
@@ -0,0 +1,27 @@
1
+ import control from './control.js'
2
+
3
+ async function controlScale () {
4
+ const WmapsControl = await control.call(this)
5
+
6
+ return class WmapsControlScale extends WmapsControl {
7
+ constructor (options) {
8
+ super(options)
9
+ this.params.noTag = true
10
+ }
11
+
12
+ async build () {
13
+ const { jsonStringify } = this.plugin.app.waibuMpa
14
+ const { isString } = this.plugin.app.bajo.lib._
15
+ const opts = {}
16
+ if (['imperial', 'metric', 'nautical'].includes(this.params.attr.unit)) opts.unit = this.params.attr.unit
17
+ if (isString(this.params.attr.maxWidth) && Number(this.params.attr.maxWidth)) opts.maxWidth = Number(this.params.attr.maxWidth)
18
+ opts.position = this.ctrlPos.includes(this.params.attr.position) ? this.params.attr.position : 'bottom-left'
19
+ this.block.control.push(`
20
+ await wmaps.createControlNative('ScaleControl', ${jsonStringify(opts, true)})
21
+ `)
22
+ this.params.html = this.writeBlock()
23
+ }
24
+ }
25
+ }
26
+
27
+ export default controlScale
@@ -0,0 +1,159 @@
1
+ import control from './control.js'
2
+ const prefix = 'csrc'
3
+
4
+ async function controlSearch () {
5
+ const WmapsControl = await control.call(this)
6
+
7
+ return class WmapsControlSearch extends WmapsControl {
8
+ constructor (options) {
9
+ super(options)
10
+ this.params.noTag = true
11
+ }
12
+
13
+ async build () {
14
+ const { generateId } = this.plugin.app.bajo
15
+ const { isString } = this.plugin.app.bajo.lib._
16
+ const { jsonStringify } = this.plugin.app.waibuMpa
17
+ const id = isString(this.params.attr.id) ? this.params.attr.id : generateId('alpha')
18
+ const opts = {}
19
+ opts.position = this.ctrlPos.includes(this.params.attr.position) ? this.params.attr.position : 'top-left'
20
+ opts.class = prefix + ' maplibregl-ctrl-group'
21
+ this.block.control.push(`
22
+ await wmaps.createControl(_.merge(${jsonStringify(opts, true)}, { builder: this.${prefix}Builder }))
23
+ `)
24
+ this.block.dataInit.push(`
25
+ this.$watch('$store.mapSearch.value', async val => {
26
+ if (_.isEmpty(val)) return wbs.notify('You need to enter a search phrase first', { type: 'danger' })
27
+ const html = await ${this.params.attr.method}(val, this.$store.mapSearch.feed)
28
+ this.$store.mapSearch.recent = html ?? ''
29
+ this.$store.mapSearch.busy = false
30
+ })
31
+ this.$watch('$store.mapSearch.recent', async val => {
32
+ val = val ?? ''
33
+ if (val === 'flying' && this.$store.mapSearch.feed === 'latLng:latLng') {
34
+ wbs.closeModal('${id}')
35
+ this.$store.mapSearch.recent = ''
36
+ }
37
+ wmpa.replaceWithComponentHtml(val, '#${id} .result div', 'div')
38
+ })
39
+ this.$watch('$store.mapSearch.feed', async val => {
40
+ this.$store.mapSearch.recent = ''
41
+ })
42
+ this.$watch('$store.mapSearch.busy', async val => {
43
+ const el = document.querySelector('#${id} .input-group .dropdown-toggle')
44
+ el.disabled = !!val
45
+ })
46
+ `)
47
+ this.block.mapLoad.push(`
48
+ await this.${prefix}Populate()
49
+ `)
50
+ this.block.reactive.push(`
51
+ async ${prefix}Builder (params) {
52
+ const body = [
53
+ '<c:button dim="height:100" flex="align-items:center justify-content:center" open="${id}">',
54
+ '<c:icon name="search" />',
55
+ '</c:button>'
56
+ ]
57
+ return [await wmpa.createComponent(body)]
58
+ },
59
+ async ${prefix}Populate () {
60
+ const feeds = await ${this.params.attr.feed}() ?? []
61
+ feeds.unshift({ code: 'latLng', name: 'Goto Latitude/Longitude', feed: { id: 'latLng', label: 'Latitude/Longitude' } })
62
+ this.$store.mapSearch.feeds = feeds
63
+ const body = feeds.map(feed => '<c:dropdown-item :class="$store.mapSearch.feed === $el.getAttribute(\\'data-code-feedid\\') ? \\'active\\' : \\'\\'" data-code-feedid="' + feed.code + ':' + feed.feed.id + '" content="' + feed.feed.label + '" @click="$store.mapSearch.feed = \\'' + feed.code + ':' + feed.feed.id + '\\'"/>')
64
+ await wmpa.addComponent(body, '#${id} .input-group .dropdown-menu', 'div')
65
+ if (!this.$store.mapSearch.feed) this.$store.mapSearch.feed = feeds[0].code + ':' + feeds[0].feed.id
66
+ const html = this.$store.mapSearch.recent ?? ''
67
+ wmpa.replaceWithComponentHtml(html, '#${id} .result div', 'div')
68
+ }
69
+ `)
70
+ const ui = await this.component.buildSentence(`
71
+ <div class="childmap maplibregl-ctrl-search">
72
+ <c:modal id="${id}" size="lg" no-header no-center x-data="{
73
+ get feedCode () {
74
+ const [code, feedId] = (this.$store.mapSearch.feed ?? '').split(':')
75
+ return code
76
+ },
77
+ get feedName () {
78
+ const [code, feedId] = (this.$store.mapSearch.feed ?? '').split(':')
79
+ const item = _.find(this.$store.mapSearch.feeds, f => f.code === code && f.feed.id === feedId)
80
+ return item ? item.feed.label : feedId
81
+ },
82
+ search () {
83
+ this.$store.mapSearch.value = this.$refs.input.value
84
+ },
85
+ clearHistory () {
86
+ wmpa.replaceWithComponentHtml('', '#${id} .result div', 'div')
87
+ },
88
+ abort () {
89
+ const endpoint = this.$store.mapSearch.busy
90
+ const status = _.get(wmpa, 'fetchingApi.' + endpoint + '.status')
91
+ const controller = _.get(wmpa, 'fetchingApi.' + endpoint + '.abortCtrl')
92
+ if (!endpoint || status !== 'fetching') return
93
+ controller.abort()
94
+ },
95
+ resetValue () {
96
+ const el = document.querySelector('#${id} input[type=search]')
97
+ el.value = ''
98
+ },
99
+ select (id) {
100
+ this.$store.mapSearch.select = id
101
+ const instance = wbs.getInstance('Modal', '${id}')
102
+ instance.hide()
103
+ }
104
+ }" x-init="$watch('$store.mapSearch.feed', resetValue)">
105
+ <c:form-input x-ref="input" type="search" dim="width:max" @keyup.enter="search()" :disabled="$store.mapSearch.busy">
106
+ <c:form-input-addon prepend>
107
+ <c:dropdown>
108
+ </c:dropdown>
109
+ </c:form-input-addon>
110
+ <c:form-input-addon prepend>
111
+ <c:span x-show="feedCode === 'latLng'"><c:icon name="arrowEnd"/></c:span>
112
+ <c:span x-show="feedCode !== 'latLng' && !$store.mapSearch.busy"><c:icon name="search"/></c:span>
113
+ <c:spinner size="sm" x-show="feedCode !== 'latLng' && $store.mapSearch.busy" text="color:primary" />
114
+ </c:form-input-addon>
115
+ <c:form-input-addon append>
116
+ <c:btn @click="abort" x-show="$store.mapSearch.busy" t:content="Abort" />
117
+ </c:form-input-addon>
118
+ </c:form-input>
119
+ <c:div margin="top-2" flex="justify-content:between">
120
+ <div>
121
+ <template x-if="feedCode !== 'latLng'"><span><c:t>Search in:</c:t> <strong><span x-html="feedName" /></strong></span></template>
122
+ <template x-if="feedCode === 'latLng'"><span><c:t>Goto:</c:t> <strong><span><c:t>Latitude/Longitude</c:t></span></strong></span></template>
123
+ </div>
124
+ <c:btn x-show="feedCode !== 'latLng'" size="sm" color="link" t:content="Clear History" @click="clearHistory()" />
125
+ </c:div>
126
+ <c:div margin="top-3" class="result"><div></div></c:div>
127
+ </c:modal>
128
+ </div>
129
+ `)
130
+ this.block.dataInit.push(`
131
+ this.$watch('$store.mapSearch.select', async val => {
132
+ if (_.isEmpty(val)) return
133
+ const [source, feedId] = (this.$store.mapSearch.feed ?? '').split(':')
134
+ const feed = _.find(this.$store.mapSearch.feeds, item => {
135
+ return item.code === source && item.feed.id === feedId
136
+ })
137
+ this.$store.mapSearch.select = null
138
+ const scope = wmpa.alpineScope()
139
+ const fn = scope[feed.feed.prefix + 'FindTarget']
140
+ if (fn) await fn.call(scope, val, feedId)
141
+ })
142
+ `)
143
+ this.block.initializing.push(`
144
+ Alpine.store('mapSearch', {
145
+ feed: Alpine.$persist('latLng:latLng').as('mapSearchFeed'),
146
+ recent: Alpine.$persist('').as('mapSearchRecent'),
147
+ feeds: [],
148
+ select: null,
149
+ value: null,
150
+ busy: false
151
+ })
152
+ `)
153
+
154
+ this.params.html = [this.writeBlock(), ui].join('\n')
155
+ }
156
+ }
157
+ }
158
+
159
+ export default controlSearch
@@ -0,0 +1,70 @@
1
+ import control from './control.js'
2
+
3
+ const prefix = 'czbp'
4
+
5
+ async function controlZbp () {
6
+ const WmapsControl = await control.call(this)
7
+
8
+ return class WmapsControlZbp extends WmapsControl {
9
+ constructor (options) {
10
+ super(options)
11
+ this.params.noTag = true
12
+ }
13
+
14
+ async build () {
15
+ const { generateId } = this.plugin.app.bajo
16
+ const { jsonStringify, minify } = this.plugin.app.waibuMpa
17
+ const { omit } = this.plugin.app.bajo.lib._
18
+ const options = omit(this.params.attr, ['octag', 'class', 'style', 'content'])
19
+ options.class = prefix + ' maplibregl-ctrl-group widget'
20
+ const id = generateId('alpha')
21
+ const tpl = await minify(await this.component.buildSentence(`
22
+ <c:grid-row font="size:5">
23
+ <c:grid-col col="4">{%= zoom %}</c:grid-col>
24
+ <c:grid-col col="4">{%= bearing %}</c:grid-col>
25
+ <c:grid-col col="4">{%= pitch %}</c:grid-col>
26
+ </c:grid-row>
27
+ <c:grid-row style="font-size:smaller">
28
+ <c:grid-col col="4" t:content="Zoom" />
29
+ <c:grid-col style="cursor:pointer" @click="reset" col="4" t:content="Bearing" />
30
+ <c:grid-col style="cursor:pointer" @click="reset" col="4" t:content="Pitch" />
31
+ </c:grid-row>
32
+ `))
33
+ this.block.dataInit.push(`
34
+ this.$watch('$store.map.zoom, $store.map.bearing, $store.map.pitch', this.${prefix}Update.bind(this))
35
+ `)
36
+ this.block.reactive.push(`
37
+ ${prefix}Tpl: _.template('${tpl}')
38
+ `, `
39
+ ${prefix}Update () {
40
+ const el = document.querySelector('#${id}')
41
+ if (!el) return
42
+ el.innerHTML = this.${prefix}Tpl({
43
+ zoom: wmpa.format(this.$store.map.zoom, 'integer'),
44
+ bearing: wmpa.format(this.$store.map.bearing, 'integer'),
45
+ pitch: wmpa.format(this.$store.map.pitch, 'integer')
46
+ })
47
+ }
48
+ `, `
49
+ async ${prefix}Builder () {
50
+ const body = ['<c:div id="${id}" margin="x-2 top-1" text="align:center" ',
51
+ 'x-data="{',
52
+ ' reset () {',
53
+ ' wmpa.alpineScopeMethod(\\'getMap.flyTo\\')({ bearing: 0, pitch: 0 }) ',
54
+ ' }',
55
+ '}" />'
56
+ ]
57
+ return await wmpa.createComponent(body)
58
+ }
59
+ `)
60
+
61
+ this.block.control.push(`
62
+ await wmaps.createControl(_.merge(${jsonStringify(options, true)}, { builder: this.${prefix}Builder, firstCall: this.${prefix}Update }))
63
+ `)
64
+
65
+ this.params.html = this.writeBlock()
66
+ }
67
+ }
68
+ }
69
+
70
+ export default controlZbp
@@ -0,0 +1,42 @@
1
+ import wmapsBase from '../wmaps-base.js'
2
+
3
+ async function control () {
4
+ const WmapsBase = await wmapsBase.call(this)
5
+
6
+ return class WmapsControl extends WmapsBase {
7
+ constructor (options) {
8
+ super(options)
9
+ this.ctrlPos = ['top-left', 'top-right', 'bottom-left', 'bottom-right']
10
+ this.params.noTag = true
11
+ }
12
+
13
+ async build () {
14
+ const { $ } = this.component
15
+ const html = []
16
+ $(`<div>${this.params.html}</div>`).find('.childmap').each(function () {
17
+ html.push($(this).prop('outerHTML'))
18
+ })
19
+ this.readBlock()
20
+ // persisting
21
+ if (this.params.attr.persist) {
22
+ this.block.initializing.push(`
23
+ Alpine.store('mapControl', {
24
+ attrib: Alpine.$persist(true).as('mapControlAttrib'),
25
+ centerPos: Alpine.$persist(true).as('mapControlCenterPos'),
26
+ fullscreen: Alpine.$persist(true).as('mapControlFullscreen'),
27
+ mousePos: Alpine.$persist(true).as('mapControlMousePos'),
28
+ nav: Alpine.$persist(true).as('mapControlNav'),
29
+ scale: Alpine.$persist(true).as('mapControlScale'),
30
+ geolocate: Alpine.$persist(true).as('mapControlGeolocate'),
31
+ ruler: Alpine.$persist(true).as('mapControlRuler'),
32
+ search: Alpine.$persist(true).as('mapControlSearch')
33
+ })
34
+ `)
35
+ }
36
+ this.params.html = this.writeBlock()
37
+ if (html.length > 0) this.params.html += '\n' + html.join('\n')
38
+ }
39
+ }
40
+ }
41
+
42
+ export default control
@@ -0,0 +1,103 @@
1
+ import wmapsBase from '../wmaps-base.js'
2
+
3
+ export function buildLayers (params) {
4
+ const { isString, map } = this.plugin.app.bajo.lib._
5
+ const { routePath } = this.plugin.app.waibu
6
+ const { attrToArray, jsonStringify } = this.plugin.app.waibuMpa
7
+
8
+ if (!isString(params.attr.layer)) return ''
9
+ const items = map(attrToArray(params.attr.layer), item => routePath(item))
10
+ return `
11
+ for (const l of ${jsonStringify(items, true)}) {
12
+ const layer = await this.loadResource(l)
13
+ layer.source = '${params.attr.name}'
14
+ map.addLayer(layer)
15
+ }
16
+ `
17
+ }
18
+
19
+ export function buildImage (params) {
20
+ const { isString, map } = this.plugin.app.bajo.lib._
21
+ const { routePath } = this.plugin.app.waibu
22
+ const { attrToArray, jsonStringify } = this.plugin.app.waibuMpa
23
+
24
+ if (!isString(params.attr.image)) return ''
25
+ const items = map(attrToArray(params.attr.image), item => routePath(item))
26
+ return `
27
+ for (const l of ${jsonStringify(items, true)}) {
28
+ let [item, name] = l.split(';')
29
+ if (!name) name = _.last(l.split('?')[0].split('#')[0].split('/')).split('.')[0]
30
+ if (map.listImages().includes(name)) continue
31
+ const resp = await map.loadImage(l)
32
+ map.addImage(name, resp.data)
33
+ }
34
+ `
35
+ }
36
+
37
+ export async function buildSrcImages (params) {
38
+ const { isString } = this.plugin.app.bajo.lib._
39
+ const { routePath, fetch } = this.plugin.app.waibu
40
+
41
+ if (!isString(params.attr.srcImages)) return
42
+ params.attr.srcImages = routePath(params.attr.srcImages)
43
+ const items = await fetch(params.attr.srcImages)
44
+ const lines = []
45
+ for (const key in items) {
46
+ lines.push(`${items[key]};${key}`)
47
+ }
48
+ params.attr.image = lines.join(' ')
49
+ return buildImage.call(this, params)
50
+ }
51
+
52
+ export function buildSource (params, extra = []) {
53
+ const { routePath } = this.plugin.app.waibu
54
+ params.attr.src = routePath(params.attr.src)
55
+ return `
56
+ const rsc = await this.loadResource('${params.attr.src}')
57
+ let data = {}
58
+ if (rsc.type === 'geojson' && rsc.data) data = rsc
59
+ else {
60
+ data.type = 'geojson'
61
+ data.data = rsc
62
+ }
63
+ ${params.attr.lineGradient ? 'data.lineMetrics = true' : ''}
64
+ ${extra.join('\n')}
65
+ map.addSource('${params.attr.name}', data)
66
+ `
67
+ }
68
+
69
+ async function layerGeojson () {
70
+ const WmapsBase = await wmapsBase.call(this)
71
+
72
+ return class WmapsLayerGeojson extends WmapsBase {
73
+ constructor (options) {
74
+ super(options)
75
+ this.params.noTag = true
76
+ }
77
+
78
+ async build () {
79
+ const { generateId } = this.plugin.app.bajo
80
+ const { isString } = this.plugin.app.bajo.lib._
81
+ const { groupAttrs } = this.plugin.app.waibuMpa
82
+ this.params.noTag = true
83
+ if (!this.params.attr.src) return
84
+ this.params.attr.name = this.params.attr.name ?? generateId('alpha')
85
+ const group = groupAttrs(this.params.attr, ['cluster'])
86
+ const cluster = []
87
+ if (group.cluster) {
88
+ cluster.push('data.cluster = true')
89
+ if (isString(group.cluster.radius)) cluster.push('data.clusterRadius = ' + Number(group.cluster.radius))
90
+ if (isString(group.cluster.maxZoom)) cluster.push('data.clusterMaxZoom = ' + Number(group.cluster.maxZoom))
91
+ }
92
+
93
+ this.block.mapLoad.push(`
94
+ ${this.params.attr.srcImages ? (await buildSrcImages.call(this, this.params)) : buildImage.call(this, this.params)}
95
+ ${buildSource.call(this, this.params, cluster)}
96
+ ${buildLayers.call(this, this.params)}
97
+ `)
98
+ this.params.html = this.writeBlock()
99
+ }
100
+ }
101
+ }
102
+
103
+ export default layerGeojson
@@ -0,0 +1,124 @@
1
+ import wmapsBase from '../wmaps-base.js'
2
+
3
+ import { buildSource } from './layer-geojson.js'
4
+
5
+ function createLayerLabel (params) {
6
+ return `{
7
+ id: '${params.attr.name}_label',
8
+ type: 'symbol',
9
+ source: '${params.attr.name}',
10
+ filter: ['!=', '${params.attr.clusterKey}', true],
11
+ layout: {
12
+ 'text-field': [
13
+ 'number-format',
14
+ ['get', '${params.attr.valueKey}'],
15
+ {'min-fraction-digits': 1, 'max-fraction-digits': 1}
16
+ ],
17
+ 'text-font': ['Open Sans Bold'],
18
+ 'text-size': 10
19
+ },
20
+ paint: {
21
+ 'text-color': [
22
+ 'case',
23
+ ['<', ['get', '${params.attr.valueKey}'], 3],
24
+ 'black',
25
+ 'white'
26
+ ]
27
+ }
28
+ }`
29
+ }
30
+
31
+ function createLayerCircle (params, filters) {
32
+ const circle = [`{
33
+ id: '${params.attr.name}_circle',
34
+ type: 'circle',
35
+ source: '${params.attr.name}',
36
+ filter: ['!=', '${params.attr.clusterKey}', true],
37
+ paint: {
38
+ 'circle-color': [
39
+ 'case',
40
+ `]
41
+ let i = 0
42
+ const keys = Object.keys(filters)
43
+ for (const key of keys) {
44
+ circle.push(`this.filter.${key},`, `this.colors[${i}],`)
45
+ i++
46
+ if (i >= keys.length - 1) break
47
+ }
48
+ circle.push(`this.colors[${i}]`, '],\'circle-opacity\': 0.6, \'circle-radius\': 12 }}')
49
+ return circle.join('\n')
50
+ }
51
+
52
+ async function layerHtmlCluster () {
53
+ const WmapsBase = await wmapsBase.call(this)
54
+
55
+ return class WmapsLayerHtmlCluster extends WmapsBase {
56
+ static scripts = [
57
+ ...super.scripts,
58
+ 'waibuMaps.asset:/js/donut-chart.js'
59
+ ]
60
+
61
+ constructor (options) {
62
+ super(options)
63
+ this.params.noTag = true
64
+ }
65
+
66
+ async build () {
67
+ const { generateId } = this.plugin.app.bajo
68
+ const { attrToArray, jsonStringify } = this.plugin.app.waibuMpa
69
+ const { fetch, routePath } = this.plugin.app.waibu
70
+ const { isString } = this.plugin.app.bajo.lib._
71
+ if (!this.params.attr.src) return
72
+ this.params.attr.name = this.params.attr.name ?? generateId('alpha')
73
+ this.params.attr.valueKey = this.params.attr.valueKey ?? ''
74
+ this.params.attr.clusterKey = this.params.attr.clusterKey ?? 'cluster'
75
+ this.params.attr.clusterIdKey = this.params.attr.clusterIdKey ?? 'clusterId'
76
+ const colors = attrToArray(this.params.attr.color ?? '#fed976 #feb24c #fd8d3c #fc4e2a #e31a1c')
77
+ this.params.attr.filter = routePath(this.params.attr.filter)
78
+ const filters = await fetch(this.params.attr.filter)
79
+ const extra = [
80
+ 'data.cluster = true',
81
+ `data.clusterRadius = ${isString(this.params.attr.clusterRadius) ? Number(this.params.attr.clusterRadius) : 80}`,
82
+ 'data.clusterProperties = {'
83
+ ]
84
+ for (const key in filters) {
85
+ extra.push(`${key}: ['+', ['case', this.filter.${key}, 1, 0]],`)
86
+ }
87
+ extra.push('}')
88
+ this.block.mapLoad.push(`
89
+ ${buildSource.call(this, this.params, extra)}
90
+ map.addLayer(
91
+ ${createLayerCircle.call(this, this.params, filters)}
92
+ )
93
+ map.addLayer(
94
+ ${createLayerLabel.call(this, this.params)}
95
+ )
96
+ map.on('data', e => {
97
+ if (e.sourceId !== '${this.params.attr.name}' || !e.isSourceLoaded) return
98
+ map.on('move', () => { this.updateMarkers() })
99
+ map.on('moveend', () => { this.updateMarkers() })
100
+ updateMarkers()
101
+ })
102
+ `)
103
+ this.block.reactive.push(
104
+ `colors: ${jsonStringify(colors, true)}`,
105
+ `filter: ${jsonStringify(filters, true)}`,
106
+ `updateMarkers () {
107
+ const filter = Alpine.raw(this.filter)
108
+ const colors = Alpine.raw(this.colors)
109
+ wmaps.updateClusterMarkers({
110
+ sourceId: '${this.params.attr.name}',
111
+ clusterKey: '${this.params.attr.clusterKey}',
112
+ clusterIdKey: '${this.params.attr.clusterIdKey}',
113
+ handler: function (props) {
114
+ return donutChart.create(props, Object.keys(filter), colors)
115
+ }
116
+ })
117
+ }`
118
+ )
119
+ this.params.html = this.writeBlock()
120
+ }
121
+ }
122
+ }
123
+
124
+ export default layerHtmlCluster