-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmapbox.ts
157 lines (144 loc) · 6.5 KB
/
mapbox.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import { Term } from '@rdfjs/types'
import { Plugin, PluginOptions } from '../plugin'
import { ShaclPropertyTemplate } from '../property-template'
import { Editor, fieldFactory } from '../theme'
import { Map, NavigationControl, FullscreenControl, LngLatBounds, LngLatLike } from 'mapbox-gl'
import MapboxDraw from '@mapbox/mapbox-gl-draw'
import mapboxGlCss from 'mapbox-gl/dist/mapbox-gl.css'
import mapboxGlDrawCss from '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'
import { Geometry, geometryToWkt, wktToGeometry } from './map-util'
const css = `
#shaclMapDialog .closeButton { position: absolute; right: 0; top: 0; z-index: 1; padding: 6px 8px; cursor: pointer; border: 0; background-color: #FFFA; font-size: 24px; }
#shaclMapDialog { padding: 0; width:90vw; height: 90vh; margin: auto; }
#shaclMapDialog::backdrop { background-color: #0007; }
#shaclMapDialog .closeButton:hover { background-color: #FFF }
#shaclMapDialog .hint { position: absolute; right: 60px; top: 3px; z-index: 1; padding: 4px 6px; background-color: #FFFA; border-radius: 4px; }
.mapboxgl-map { min-height: 300px; }
#shaclMapDialogContainer { width:100%; height: 100% }
`
const dialogTemplate = `
<dialog id="shaclMapDialog" onclick="event.target==this && this.close()">
<div id="shaclMapDialogContainer"></div>
<div class="hint">ⓘ Draw a polygon or point, then close dialog</div>
<button class="closeButton" type="button" onclick="this.parentElement.close()">✕</button>
</dialog>`
export class MapboxPlugin extends Plugin {
map: Map | undefined
draw: MapboxDraw | undefined
currentEditor: Editor | undefined
apiKey: string
constructor(options: PluginOptions, apiKey: string) {
super(options, mapboxGlCss + '\n' + mapboxGlDrawCss + '\n' + css)
this.apiKey = apiKey
}
initEditMode(form: HTMLElement): HTMLDialogElement {
form.insertAdjacentHTML('beforeend', dialogTemplate)
const container = form.querySelector('#shaclMapDialogContainer') as HTMLElement
this.map = new Map({
container: container,
style: 'mapbox://styles/mapbox/satellite-streets-v11',
zoom: 5,
center: { lng: 8.657238961696038, lat: 49.87627570549512 },
attributionControl: false,
accessToken: this.apiKey
})
this.draw = new MapboxDraw({
displayControlsDefault: false,
controls: { point: true, polygon: true }
})
this.map.addControl(new NavigationControl(), 'top-left')
this.map.addControl(this.draw, 'top-left')
this.map.on('idle', () => {
// this fixes wrong size of canvas
this.map!.resize()
})
// @ts-ignore
this.map.on('draw.create', () => this.deleteAllButLastDrawing())
const dialog = form.querySelector('#shaclMapDialog') as HTMLDialogElement
dialog.addEventListener('close', () => {
const scrollY = document.body.style.top
document.body.style.position = ''
document.body.style.top = ''
window.scrollTo(0, parseInt(scrollY || '0') * -1)
// set wkt in editor
const data = this.draw!.getAll()
if (data && data.features.length && this.currentEditor) {
const geometry = data.features[0].geometry as Geometry
if (geometry.coordinates?.length) {
const wkt = geometryToWkt(geometry)
this.currentEditor.value = wkt
this.currentEditor.dispatchEvent(new Event('change', { bubbles: true }))
}
}
})
return dialog
}
createEditor(template: ShaclPropertyTemplate, value?: Term): HTMLElement {
let dialog = template.config.form.querySelector('#shaclMapDialog') as HTMLDialogElement
if (!dialog) {
dialog = this.initEditMode(template.config.form)
}
const button = template.config.theme.createButton('Open map...', false)
button.style.marginLeft = '5px'
button.classList.add('open-map-button')
button.onclick = () => {
this.currentEditor = instance.querySelector('.editor') as Editor
this.draw?.deleteAll()
const wkt = this.currentEditor.value || ''
const geometry = wktToGeometry(wkt)
if (geometry && geometry.coordinates?.length) {
this.draw?.add(geometry)
this.fitToGeometry(this.map!, geometry)
} else {
this.map?.setZoom(5)
}
document.body.style.top = `-${window.scrollY}px`
document.body.style.position = 'fixed'
dialog.showModal()
}
const instance = fieldFactory(template, value || null)
instance.appendChild(button)
return instance
}
createViewer(template: ShaclPropertyTemplate, value: Term): HTMLElement {
const container = document.createElement('div')
const geometry = wktToGeometry(value.value)
if (geometry?.coordinates?.length) {
// wait for container to be available in DOM
setTimeout(() => {
const draw = new MapboxDraw({ displayControlsDefault: false })
const map = new Map({
container: container,
style: 'mapbox://styles/mapbox/satellite-streets-v11',
zoom: 5,
attributionControl: false,
accessToken: this.apiKey
})
map.addControl(draw)
map.addControl(new FullscreenControl())
draw.add(geometry)
this.fitToGeometry(map, geometry)
})
}
return container
}
fitToGeometry(map: Map, geometry: Geometry) {
if (typeof geometry.coordinates[0] === 'number') {
// e.g. Point
map.setCenter(geometry.coordinates as LngLatLike)
map.setZoom(15)
} else {
// e.g. Polygon
const bounds = geometry.coordinates[0].reduce((bounds, coord) => {
return bounds.extend(coord as mapboxgl.LngLatLike)
}, new LngLatBounds(geometry.coordinates[0][0] as mapboxgl.LngLatLike, geometry.coordinates[0][0] as mapboxgl.LngLatLike))
map.fitBounds(bounds, { padding: 20, animate: false })
}
}
deleteAllButLastDrawing() {
const data = this.draw!.getAll()
for (let i = 0; i < data.features.length - 1; i++) {
this.draw!.delete(data.features[i].id as string)
}
}
}