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,44 @@
1
+ export const opts = {
2
+ boolTrue: ['antialias', 'hash', 'maplibreLogo'],
3
+ boolFalse: ['noBoxZoom', 'noDoubleClickZoom', 'noDoubleClickZoom', 'noDragPan', 'noDragRotate',
4
+ 'noInteractive', 'noKeyboard', 'noPitchWithRotate', 'noRenderWorldCopies', 'noScrollZoom',
5
+ 'noTouchPitch', 'noTouchZoomRotate', 'noTrackResize', 'noValidateStyle'],
6
+ number: ['zoom', 'bearing', 'bearingSnap', 'clickTolerance', 'fadeDuration', 'maxPitch',
7
+ 'maxZoom', 'minPitch', 'minZoom', 'pitch'],
8
+ array: ['center', 'bounds', 'maxBounds'],
9
+ string: ['basemap', 'attribution']
10
+ }
11
+
12
+ async function options (params = {}) {
13
+ const { camelCase, isString } = this.plugin.app.bajo.lib._
14
+ const { attrToArray } = this.plugin.app.waibuMpa
15
+ const { routePath } = this.plugin.app.waibu
16
+ const mapOpts = this.plugin.app.waibuMaps.getConfig().mapOptions
17
+ mapOpts.container = params.attr.id
18
+ const { $ } = this.component
19
+ for (const key in params.attr) {
20
+ const val = params.attr[key]
21
+ if (val === true) {
22
+ if (opts.boolTrue.includes(key)) mapOpts[key] = true
23
+ if (opts.boolFalse.includes(key)) mapOpts[camelCase(key.slice(2))] = false
24
+ } else {
25
+ if (opts.number.includes(key)) mapOpts[key] = Number(val)
26
+ else if (opts.array.includes(key)) mapOpts[key] = attrToArray(val).map(v => Number(v))
27
+ else if (opts.string.includes(key)) mapOpts[key] = val
28
+ }
29
+ }
30
+ if (params.attr.noBasemap) {
31
+ delete mapOpts.style
32
+ } else {
33
+ if (isString(params.attr.basemap)) mapOpts.style = params.attr.basemap
34
+ else mapOpts.style = 'waibuMaps:/default-style.json'
35
+ mapOpts.style = routePath(mapOpts.style)
36
+ }
37
+ mapOpts.attributionControl = true
38
+ $(`<div>${params.html}</div>`).find('script[block="control"]:contains(\'AttributionControl\')').each(function () {
39
+ mapOpts.attributionControl = false
40
+ })
41
+ return mapOpts
42
+ }
43
+
44
+ export default options
@@ -0,0 +1,139 @@
1
+ import wmapsBase from '../wmaps-base.js'
2
+
3
+ import options from './map/options.js'
4
+
5
+ /*
6
+ const loadResource = `async loadResource (src) {
7
+ const resp = await fetch(src)
8
+ if (!resp.ok) throw new Error('Can\\'t load resource: ' + src)
9
+ return resp.json()
10
+ }`
11
+ */
12
+
13
+ async function map () {
14
+ const WmapsBase = await wmapsBase.call(this)
15
+
16
+ return class WmapsMap extends WmapsBase {
17
+ constructor (options) {
18
+ super(options)
19
+ this.readBlock()
20
+ }
21
+
22
+ async build () {
23
+ const { generateId } = this.plugin.app.bajo
24
+ const { without, omit, isString } = this.plugin.app.bajo.lib._
25
+ const { jsonStringify } = this.plugin.app.waibuMpa
26
+ const { $ } = this.component
27
+
28
+ this.params.attr.id = 'map' + (isString(this.params.attr.id) ? this.params.attr.id : generateId('alpha'))
29
+ this.params.tag = 'div'
30
+ this.params.attr['x-data'] = this.params.attr.id
31
+ this.params.attr['@keyup'] = 'onKeyup'
32
+ const mapOptions = await options.call(this, this.params)
33
+ this.block.reactive.push(`
34
+ async windowLoad () {
35
+ const mapOpts = ${jsonStringify(mapOptions, true)}
36
+ ${this.block.mapOptions.join('\n')}
37
+ await this.run(new maplibregl.Map(mapOpts))
38
+ }
39
+ `)
40
+ this.params.attr['@load.window'] = 'await windowLoad()'
41
+ this.params.append = `<script>
42
+ document.addEventListener('alpine:init', () => {
43
+ Alpine.data('${this.params.attr.id}', () => {
44
+ let map
45
+ let wmaps
46
+ ${this.block.nonReactive.join('\n')}
47
+ return {
48
+ init () {
49
+ ${this.block.dataInit.join('\n')}
50
+ this.$watch('$store.wmpa.reqAborted', val => {
51
+ if (!val) return
52
+ const text = _.get(wmpa, 'fetchingApi.' + val + '.status')
53
+ if (text.startsWith('abort:')) {
54
+ const [, msg] = text.split(':')
55
+ wbs.notify(msg, { type: 'warning' }).then()
56
+ }
57
+ })
58
+ },
59
+ ${this.block.reactive.join(',\n')},
60
+ async onMapLoad (evt) {
61
+ ${this.block.mapLoad.join('\n')}
62
+ this.onMapStyle()
63
+ },
64
+ async onMapStyle () {
65
+ ${this.block.mapStyle.join('\n')}
66
+ },
67
+ async onMissingImage (evt) {
68
+ ${this.block.missingImage.join('\n')}
69
+ },
70
+ onLayerVisibility (layerId, shown) {
71
+ if (!shown) {
72
+ if (wmaps.popup) {
73
+ const el = wmaps.popup.getElement()
74
+ if (el && el.classList.contains('popup-layer-' + layerId)) wmaps.popup.remove()
75
+ }
76
+ }
77
+ ${this.block.layerVisibility.join('\n')}
78
+ },
79
+ async onKeyup (evt) {
80
+ if (evt.key === 'Escape') {
81
+ if (wmaps.popup) wmaps.popup.remove()
82
+ }
83
+ ${this.block.keyup.join('\n')}
84
+ },
85
+ async run (instance) {
86
+ map = instance
87
+ wmaps = new WaibuMaps(instance, this)
88
+ map.on('moveend', evt => {
89
+ Alpine.store('map').center = evt.target.getCenter().toArray()
90
+ Alpine.store('map').zoom = evt.target.getZoom()
91
+ Alpine.store('map').bearing = evt.target.getBearing()
92
+ Alpine.store('map').pitch = evt.target.getPitch()
93
+ })
94
+ ${this.block.mapExtend.join('\n')}
95
+ ${this.block.control.join('\n')}
96
+ ${this.block.run.join('\n')}
97
+ map.on('styledataloading', () => {
98
+ map.once('styledata', this.onMapStyle.bind(this))
99
+ })
100
+ map.on('styleimagemissing', this.onMissingImage.bind(this))
101
+ map.on('load', this.onMapLoad.bind(this))
102
+ }
103
+ }
104
+ })
105
+ ${this.block.init.join('\n')}
106
+ })
107
+ document.addEventListener('alpine:initializing', () => {
108
+ const props = {
109
+ id: '${this.params.attr.id}',
110
+ degree: Alpine.$persist('DMS').as('mapDegree'),
111
+ measure: Alpine.$persist('nautical').as('mapMeasure'),
112
+ zoomScrollCenter: Alpine.$persist(false).as('mapZoomScrollCenter'),
113
+ noMapRotate: Alpine.$persist(false).as('mapNoMapRotate'),
114
+ center: Alpine.$persist(null).as('mapCenter'),
115
+ zoom: Alpine.$persist(null).as('mapZoom'),
116
+ bearing: Alpine.$persist(null).as('mapBearing'),
117
+ pitch: Alpine.$persist(null).as('mapPitch')
118
+ }
119
+ for (const ctrl of ${jsonStringify(WmapsBase.controls, true)}) {
120
+ const name = 'ctrl' + wmpa.pascalCase(ctrl)
121
+ props[name] = Alpine.$persist(true).as('map' + _.upperFirst(name))
122
+ }
123
+ Alpine.store('map', props)
124
+ ${this.block.initializing.join('\n')}
125
+ })
126
+ </script>`
127
+ const html = []
128
+ $(`<div>${this.params.html}</div>`).find('.childmap').each(function () {
129
+ html.push($(this).prop('outerHTML'))
130
+ })
131
+ this.params.html = html.join('\n')
132
+ const keys = without(Object.keys(mapOptions), 'style')
133
+ const omitted = ['noBasemap', ...keys]
134
+ this.params.attr = omit(this.params.attr, omitted)
135
+ }
136
+ }
137
+ }
138
+
139
+ export default map
@@ -0,0 +1,20 @@
1
+ import wmapsBase from '../wmaps-base.js'
2
+
3
+ async function script () {
4
+ const WmapsBase = await wmapsBase.call(this)
5
+
6
+ return class WmapsScript extends WmapsBase {
7
+ constructor (options) {
8
+ super(options)
9
+ this.params.noTag = true
10
+ }
11
+
12
+ async build () {
13
+ const type = WmapsBase.blockTypes.includes(this.params.attr.type) ? this.params.attr.type : 'run'
14
+ this.block[type].push(this.params.html)
15
+ this.params.html = this.writeBlock()
16
+ }
17
+ }
18
+ }
19
+
20
+ export default script
@@ -0,0 +1,10 @@
1
+ import wmapsBase from '../wmaps-base.js'
2
+
3
+ async function template () {
4
+ const WmapsBase = await wmapsBase.call(this)
5
+
6
+ return class WmapsTemplate extends WmapsBase {
7
+ }
8
+ }
9
+
10
+ export default template
@@ -0,0 +1,40 @@
1
+ async function wmapsBase () {
2
+ return class WmapsBase extends this.baseFactory {
3
+ static scripts = [...super.scripts,
4
+ 'waibuMaps.asset:/js/lib/worker-timers.js',
5
+ 'waibuMaps.virtual:/maplibre/maplibre-gl.js',
6
+ 'waibuMaps:/wmaps.js'
7
+ ]
8
+
9
+ static css = [...super.css,
10
+ 'waibuMaps.virtual:/maplibre/maplibre-gl.css',
11
+ 'waibuMaps.asset:/css/wmaps.css'
12
+ ]
13
+
14
+ constructor (options) {
15
+ super(options)
16
+ this.blockTypes = [...this.blockTypes,
17
+ 'mapLoad', 'control', 'mapOptions', 'mapStyle', 'layerVisibility', 'missingImage',
18
+ 'mapExtend'
19
+ ]
20
+ this.init()
21
+ }
22
+
23
+ static controls = ['csrc', 'navigation-control', 'crlr', 'scale-control', 'attribution-control',
24
+ 'fullscreen-control', 'geolocate-control', 'czbp', 'cmp']
25
+
26
+ async getWmapsTemplate (html, type, defEmpty = '') {
27
+ const { trim, isEmpty } = this.plugin.app.bajo.lib._
28
+ const { minify } = this.plugin.app.waibuMpa
29
+ const { $ } = this.component
30
+ let tpl = trim($(`<div>${html}</div>`).find(`wmaps-template[type="${type}"]`).prop('innerHTML'))
31
+ if (isEmpty(tpl)) {
32
+ if (defEmpty === 'popup') tpl = '<div class="px-3 py-2">{%= name %}</div>'
33
+ else tpl = defEmpty
34
+ }
35
+ return await minify(tpl)
36
+ }
37
+ }
38
+ }
39
+
40
+ export default wmapsBase
@@ -0,0 +1,10 @@
1
+ <c:div dim="height:100" flex="align-items:center justify-content:center" data-bs-toggle="dropdown" data-bs-auto-close="outside">
2
+ {% if (image !== '') { %}
3
+ <c:img src="{%= image %}" />
4
+ {% } else { %}
5
+ <c:icon oname="{%= icon %}" @click="{%= click %}" />
6
+ {% } %}
7
+ </c:div>
8
+ <c:div class="dropdown-menu">
9
+ {%= html %}
10
+ </c:div>
@@ -0,0 +1,13 @@
1
+ export const glyphUrl = 'waibuMaps.asset:/font/noto_sans_regular.pbf?s={fontstack}&r={range}'
2
+
3
+ const wmaps = {
4
+ url: '/default-style.json',
5
+ method: 'GET',
6
+ handler: async function (req, reply) {
7
+ const { routePath } = this.app.waibu
8
+ const glyphs = routePath(glyphUrl)
9
+ return reply.view('waibuMaps.template:/default-style.json', { glyphs })
10
+ }
11
+ }
12
+
13
+ export default wmaps
@@ -0,0 +1,13 @@
1
+ import { glyphUrl } from './default-style.js'
2
+
3
+ const wmaps = {
4
+ url: '/wmaps.js',
5
+ method: 'GET',
6
+ handler: async function (req, reply) {
7
+ const { routePath } = this.app.waibu
8
+ const glyphs = routePath(glyphUrl)
9
+ return reply.view('waibuMaps.template:/wmaps.js', { glyphs })
10
+ }
11
+ }
12
+
13
+ export default wmaps
@@ -0,0 +1,20 @@
1
+ {
2
+ "version": 8,
3
+ "glyphs": "<%= glyphs %>",
4
+ "sources": {
5
+ "osm": {
6
+ "type": "raster",
7
+ "tiles": ["https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"],
8
+ "tileSize": 256,
9
+ "attribution": "&copy; OpenStreetMap Contributors",
10
+ "maxzoom": 19
11
+ }
12
+ },
13
+ "layers": [
14
+ {
15
+ "id": "osm",
16
+ "type": "raster",
17
+ "source": "osm"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,320 @@
1
+ /* global maplibregl, geolib, _, wmpa, WorkerTimers, turf, Alpine */
2
+
3
+ class WaibuMaps { // eslint-disable-line no-unused-vars
4
+ constructor (map, scope) {
5
+ this.map = map
6
+ this.scope = scope
7
+ this.markers = {}
8
+ this.markersOnScreen = {}
9
+ this.popup = null
10
+ }
11
+
12
+ async handleClusterClick (layerId, clusterId = 'cluster_id') {
13
+ this.handlePointer(layerId)
14
+ this.map.on('click', layerId, async (e) => {
15
+ const features = this.map.queryRenderedFeatures(e.point, {
16
+ layers: [layerId]
17
+ })
18
+ const id = features[0].properties[clusterId]
19
+ const layer = this.map.getLayer(layerId)
20
+ const zoom = await this.map.getSource(layer.source).getClusterExpansionZoom(id)
21
+ this.map.easeTo({
22
+ center: features[0].geometry.coordinates,
23
+ zoom
24
+ })
25
+ })
26
+ }
27
+
28
+ async createPopupHtml ({ props, handler, coordinates, layerId }, evt) {
29
+ let html = _.isString(handler) ? handler : undefined
30
+ if (_.isFunction(handler)) html = await handler.call(this, { props, coordinates, layerId }, evt)
31
+ if (!html) html = props.name ?? props.title ?? props.description ?? ''
32
+ return html
33
+ }
34
+
35
+ getEventCoordinates (evt) {
36
+ let coordinates = evt.features[0].geometry.coordinates.slice()
37
+ if (_.isArray(coordinates[0])) {
38
+ const centroid = turf.centroid(evt.features[0])
39
+ coordinates = centroid.geometry.coordinates
40
+ }
41
+ while (Math.abs(evt.lngLat.lng - coordinates[0]) > 180) {
42
+ coordinates[0] += evt.lngLat.lng > coordinates[0] ? 360 : -360
43
+ }
44
+ return coordinates
45
+ }
46
+
47
+ async extractPopup ({ evt, layerId, handler, props, coordinates }) {
48
+ props = props ?? evt.features[0].properties
49
+ coordinates = coordinates ?? this.getEventCoordinates(evt)
50
+ const html = await this.createPopupHtml({ props, handler, coordinates, layerId }, evt)
51
+ return { props, coordinates, html }
52
+ }
53
+
54
+ createPopup () {
55
+ if (!this.popup) {
56
+ this.popup = new maplibregl.Popup({
57
+ closeButton: false,
58
+ closeOnClick: false
59
+ })
60
+ }
61
+ return this.popup
62
+ }
63
+
64
+ async handleNonClusterClick (layerId, handler = 'name') {
65
+ if (handler === true) handler = 'name'
66
+ this.handlePointer(layerId)
67
+ this.map.on('click', layerId, async evt => {
68
+ const { coordinates, html, props } = await this.extractPopup({ evt, layerId, handler })
69
+ const popup = this.createPopup()
70
+ popup._props = props
71
+ popup._coordinates = coordinates
72
+ popup
73
+ .setLngLat(coordinates)
74
+ .setHTML(html)
75
+ .addTo(this.map)
76
+ .addClassName('popup-layer-' + layerId)
77
+ .addClassName('popup-target-' + props.id + '-' + props.feed)
78
+ })
79
+ this.map.on('click', () => {
80
+ if (this.popup) this.popup.remove()
81
+ })
82
+ }
83
+
84
+ popup ({ layerId, props, html, coordinates }) {
85
+ return new maplibregl.Popup({ className: 'popup-layer-' + layerId + ' popup-target-' + props.id })
86
+ .setLngLat(coordinates)
87
+ .setHTML(html)
88
+ .addTo(this.map)
89
+ }
90
+
91
+ updateClusterMarkers ({ sourceId, clusterKey = 'cluster', clusterIdKey = 'clusterId', handler }) {
92
+ if (!handler) return
93
+ const newMarkers = {}
94
+ const features = this.map.querySourceFeatures(sourceId)
95
+
96
+ for (let i = 0; i < features.length; i++) {
97
+ const coords = features[i].geometry.coordinates
98
+ const props = features[i].properties
99
+ if (!props[clusterKey]) continue
100
+ const id = props[clusterIdKey]
101
+
102
+ let marker = this.markers[id]
103
+ if (!marker) {
104
+ const el = handler(props)
105
+ marker = this.markers[id] = new maplibregl.Marker({
106
+ element: el
107
+ }).setLngLat(coords)
108
+ }
109
+ newMarkers[id] = marker
110
+
111
+ if (!this.markersOnScreen[id]) marker.addTo(this.map)
112
+ }
113
+ for (const id in this.markersOnScreen) {
114
+ if (!newMarkers[id]) this.markersOnScreen[id].remove()
115
+ }
116
+ this.markersOnScreen = newMarkers
117
+ }
118
+
119
+ handlePointer (layerId) {
120
+ this.map.on('mouseenter', layerId, () => {
121
+ this.map.getCanvas().style.cursor = 'pointer'
122
+ })
123
+ this.map.on('mouseleave', layerId, () => {
124
+ this.map.getCanvas().style.cursor = ''
125
+ })
126
+ }
127
+
128
+ closePopup () {
129
+ if (this.popup) this.popup.remove()
130
+ }
131
+
132
+ async loadImage (src) {
133
+ if (_.isString(src)) {
134
+ const [url, id] = src.split(';')
135
+ src = { url, id }
136
+ }
137
+ if (!src.id) src.id = _.last(src.url.split('?')[0].split('#')[0].split('/')).split('.')[0]
138
+ if (this.map.listImages().includes(src.id)) return
139
+ const image = await this.map.loadImage(src.url)
140
+ this.map.addImage(src.id, image.data)
141
+ }
142
+
143
+ async loadImages (sources, fetch = true) {
144
+ for (const src of sources) {
145
+ if (fetch) {
146
+ const data = await wmpa.fetchApi(src)
147
+ if (_.isEmpty(data)) continue
148
+ for (const d of data) {
149
+ this.loadImage(d)
150
+ }
151
+ } else await this.loadImage(src)
152
+ }
153
+ }
154
+
155
+ async createControl (options = {}) {
156
+ const ctrl = new WaibuMapsControl(options)
157
+ ctrl.scope = this.scope
158
+ if (options.builder) {
159
+ if (_.isArray(options.builder)) ctrl.panels = options.builder
160
+ else if (_.isString(options.builder)) {
161
+ ctrl.panels = await wmpa.createComponent(options.builder)
162
+ } else {
163
+ const fn = options.builder.bind(ctrl.scope)
164
+ ctrl.panels = await fn(options.params)
165
+ }
166
+ }
167
+ this.map.addControl(ctrl)
168
+ if (options.firstCall) options.firstCall.call(ctrl.scope)
169
+ return ctrl
170
+ }
171
+
172
+ async createControlNative (className, options = {}) {
173
+ const name = wmpa.pascalCase(className)
174
+ const ctrl = new maplibregl[name](options)
175
+ let type = options.classSelector
176
+ if (!type) {
177
+ const types = _.kebabCase(className).split('-')
178
+ types.pop()
179
+ type = 'maplibregl-ctrl-' + types.join('-')
180
+ }
181
+ this.map.addControl(ctrl, options.position)
182
+ let el = document.querySelector('#' + this.map._container.id + ' .' + type)
183
+ if (el) {
184
+ if (options.classGroup) el = el.closest('.maplibregl-ctrl-group')
185
+ el.setAttribute('oncontextmenu', 'return false')
186
+ el.setAttribute('x-data', '')
187
+ el.setAttribute('x-show', '$store.map.ctrl' + name)
188
+ }
189
+ }
190
+ }
191
+
192
+ class WaibuMapsUtil {
193
+ constructor () {
194
+ this.defSources = {
195
+ type: 'raster',
196
+ tileSize: 256,
197
+ maxzoom: 19,
198
+ attribution: 'Waibu Maps'
199
+ }
200
+
201
+ this.defLayer = {
202
+ type: 'raster'
203
+ }
204
+
205
+ this.defStyle = {
206
+ version: 8,
207
+ glyphs: '<%= glyphs %>',
208
+ sources: {},
209
+ layers: []
210
+ }
211
+ }
212
+
213
+ decToDms (decimal, opts = {}) {
214
+ if (opts === true || opts === false) opts = { isLng: opts }
215
+ opts.north = opts.north ?? 'N'
216
+ opts.south = opts.south ?? 'S'
217
+ opts.east = opts.east ?? 'E'
218
+ opts.west = opts.west ?? 'W'
219
+ const result = geolib.decimalToSexagesimal(decimal)
220
+ if (opts.isLng) return result + ' ' + (decimal >= 0 ? opts.east : opts.west)
221
+ else return result + ' ' + (decimal >= 0 ? opts.north : opts.south)
222
+ }
223
+
224
+ getSourceId (id, ext) {
225
+ return 's-' + _.kebabCase(id) + (_.isEmpty(ext) ? '' : ('-' + ext))
226
+ }
227
+
228
+ getLayerId (id, ext = '') {
229
+ return 'l-' + _.kebabCase(id) + (_.isEmpty(ext) ? '' : ('-' + ext))
230
+ }
231
+
232
+ getSourceLayerIds (id, ext) {
233
+ return [this.getSourceId(id, ext), this.getLayerId(id, ext)]
234
+ }
235
+
236
+ srcAsStyle (src) {
237
+ if ((_.isPlainObject(src) && src.version && src.sources)) return src
238
+ const result = {}
239
+ if (_.isString(src)) {
240
+ const url = new URL(src)
241
+ const ext = _.last(url.pathname.split('.'))
242
+ if (ext === 'json') return src
243
+ const domain = _.camelCase(url.hostname)
244
+ const sources = {}
245
+ sources[domain] = _.merge({}, this.defSources, { tiles: [src], attribution: url.searchParams.get('attribution') ?? '' })
246
+ const layers = [_.merge({}, this.defLayer, { id: domain, source: domain })]
247
+ _.merge(result, this.defStyle, { sources, layers })
248
+ } else {
249
+ if (src.type === 'VECTOR') return src.url
250
+ if (!['RASTER'].includes(src.type)) throw new Error('Invalid source type')
251
+ const sources = {}
252
+ const sourceId = this.getSourceId(src.code)
253
+ const layerId = this.getLayerId(src.code)
254
+ sources[sourceId] = _.merge({}, this.defSources, { name: src.name, provider: src.provider, tiles: [src.url], attribution: src.attribution, minzoom: src.minZoom ?? 1, maxzoom: src.maxZoom ?? 19 })
255
+ const layers = [_.merge({}, this.defLayer, { id: layerId, source: sourceId })]
256
+ _.merge(result, this.defStyle, { sources, layers })
257
+ }
258
+ return result
259
+ }
260
+ }
261
+
262
+ class WaibuMapsControl { // eslint-disable-line no-unused-vars
263
+ constructor (options = {}) {
264
+ this.position = options.position ?? 'top-right'
265
+ this.class = options.class
266
+ }
267
+
268
+ createControl () {
269
+ this.container = document.createElement('div')
270
+ // this.container.setAttribute('oncontextmenu', 'return false')
271
+ this.container.classList.add('maplibregl-ctrl', 'maplibregl-ctrl-wmaps')
272
+ if (this.class) {
273
+ const classes = _.without(this.class.split(' '), '', null, undefined)
274
+ if (classes.length > 0) {
275
+ this.container.classList.add(...classes)
276
+ const ctrlName = 'ctrl' + wmpa.pascalCase(classes[0])
277
+ if (_.has(Alpine.store('map'), ctrlName)) {
278
+ this.container.setAttribute('x-show', 'Alpine.store(\'map\')[\'' + ctrlName + '\']') // first class will be used as control switch class
279
+ }
280
+ }
281
+ }
282
+ if (this.panels) {
283
+ if (!_.isArray(this.panels)) this.panels = [this.panels]
284
+ if (this.panels.length > 0) {
285
+ for (const panel of this.panels) {
286
+ this.container.appendChild(panel)
287
+ }
288
+ }
289
+ }
290
+ }
291
+
292
+ onAdd (map) {
293
+ this.map = map
294
+ this.createControl()
295
+ return this.container
296
+ }
297
+
298
+ onRemove () {
299
+ this.container.parentNode.removeChild(this.container)
300
+ this.map = undefined
301
+ this.scope = undefined
302
+ }
303
+
304
+ getDefaultPosition () {
305
+ return this.position
306
+ }
307
+ }
308
+
309
+ const wmapsUtil = new WaibuMapsUtil() // eslint-disable-line no-unused-vars
310
+
311
+ // patch
312
+ window.setInterval = WorkerTimers.setInterval
313
+ window.clearInterval = WorkerTimers.clearInterval
314
+ window.setTimeout = WorkerTimers.setTimeout
315
+ window.clearTimeout = WorkerTimers.clearTimeout
316
+
317
+ const _warn = console.warn
318
+ console.warn = (...items) => {
319
+ if (!(items[0] ?? '').includes('could not be loaded. Please make sure you have added the image with')) _warn(...items)
320
+ }
@@ -0,0 +1,3 @@
1
+ .maplibregl-ctrl-buttons button i {
2
+ font-size: 16pt;
3
+ }
@@ -0,0 +1,17 @@
1
+ .maplibregl-ctrl-center {
2
+ z-index: 1000;
3
+ position: absolute;
4
+ width: 100%;
5
+ height: 100%;
6
+ top: 0;
7
+ pointer-events: none;
8
+ display: flex;
9
+ justify-content: center;
10
+ align-items: center;
11
+ }
12
+
13
+ .maplibregl-ctrl-center div {
14
+ width: 50px;
15
+ height: 50px;
16
+ background-image: url(../image/center.svg)
17
+ }
@@ -0,0 +1,7 @@
1
+ .maplibregl-ctrl-loader {
2
+ z-index: 1000;
3
+ position: absolute;
4
+ width: 100%;
5
+ top: 0px;
6
+ pointer-events: none;
7
+ }