Skip to content

Commit

Permalink
improved mapbox plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
s-tittel committed Sep 7, 2023
1 parent 91a644a commit 2b30fee
Show file tree
Hide file tree
Showing 9 changed files with 375 additions and 65 deletions.
247 changes: 245 additions & 2 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"@mapbox/mapbox-gl-draw": "^1.4.2",
"mapbox-gl": "^2.15.0",
"n3": "^1.17.0",
"rdf-validate-shacl": "^0.4.5",
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class Config {
loader = new Loader(this)
classInstanceProvider: ClassInstanceProvider | undefined
prefixes: Prefixes = {}
plugins: Plugins = {}
plugins = new Plugins()

dataGraph = new Store()
lists: Record<string, Term[]> = {}
Expand Down
24 changes: 5 additions & 19 deletions src/editors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export function createNumberEditor(template: ShaclPropertyTemplate, value?: Term
return createDefaultTemplate(template, editor, value)
}

export type InputListEntry = { value: Term, label?: string }
export type InputListEntry = { value: Term | string, label?: string }

export function createListEditor(template: ShaclPropertyTemplate, listEntries: InputListEntry[], value?: Term): HTMLElement {
const editor = document.createElement('select')
Expand All @@ -175,9 +175,10 @@ export function createListEditor(template: ShaclPropertyTemplate, listEntries: I

for (const item of listEntries) {
const option = document.createElement('option')
option.innerHTML = item.label ? item.label : item.value.value
option.value = item.value.value
if (value && value.equals(item.value)) {
const itemValue = (typeof item.value === 'string') ? item.value : item.value.value
option.innerHTML = item.label ? item.label : itemValue
option.value = itemValue
if (value && value.value === itemValue) {
option.selected = true
}
editor.options.add(option)
Expand All @@ -188,21 +189,6 @@ export function createListEditor(template: ShaclPropertyTemplate, listEntries: I
return result
}

function setListEntries(select: HTMLSelectElement, list: InputListEntry[], value?: Term) {
for (const item of list) {
const option = document.createElement('option')
option.innerHTML = item.label ? item.label : item.value.value
option.value = item.value.value
if (value && value.equals(item.value)) {
option.selected = true
}
select.options.add(option)
}
if (value) {
select.value = value.value
}
}

export function toRDF(editor: Editor): Literal | NamedNode | undefined {
let datatype = editor['shacl-datatype']
let value: number | string = editor.value
Expand Down
2 changes: 1 addition & 1 deletion src/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class ShaclForm extends HTMLElement {
}

public registerPlugin(plugin: Plugin) {
this.config.plugins[plugin.predicate] = plugin
this.config.plugins.register(plugin)
this.initialize()
}

Expand Down
38 changes: 28 additions & 10 deletions src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
import { ShaclPropertyTemplate } from './property-template'
import { InputListEntry } from './editors'
import { NamedNode } from 'n3'
import { Term } from '@rdfjs/types'
import { findInstancesOf } from './util';
import { Config } from './config';
import { SHAPES_GRAPH } from './constants';

export type Plugins = {
[predicate: string]: Plugin;
export class Plugins {
private plugins: Record<string, Plugin> = {}

register(plugin: Plugin) {
this.plugins[`${plugin.predicate}:${plugin.datatype}`] = plugin
}

find(predicate: string | undefined, datatype: string | undefined): Plugin | undefined {
let plugin = this.plugins[`${predicate}:${datatype}`]
if (plugin) {
return plugin
}
plugin = this.plugins[`${predicate}:${undefined}`]
if (plugin) {
return plugin
}
return this.plugins[`${undefined}:${datatype}`]
}
}

export type PluginOptions = {
predicate?: string
datatype?: string
}

export abstract class Plugin {
predicate: string
predicate: string | undefined
datatype: string | undefined

constructor(predicate: string) {
this.predicate = predicate
constructor(options: PluginOptions) {
this.predicate = options.predicate
this.datatype = options.datatype
}

abstract createInstance(template: ShaclPropertyTemplate, value?: Term): HTMLElement
Expand Down
6 changes: 3 additions & 3 deletions src/plugins/fixed-list.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Plugin } from '../plugin'
import { Plugin, PluginOptions } from '../plugin'
import { Term } from '@rdfjs/types'

import { ShaclPropertyTemplate } from '../property-template'
Expand All @@ -7,8 +7,8 @@ import { createListEditor, InputListEntry } from '../editors'
export class FixedListPlugin extends Plugin {
entries: InputListEntry[]

constructor(predicate: string, entries: InputListEntry[]) {
super(predicate)
constructor(options: PluginOptions, entries: InputListEntry[]) {
super(options)
this.entries = entries
}

Expand Down
118 changes: 90 additions & 28 deletions src/plugins/mapbox.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import { Term } from '@rdfjs/types'
import { Plugin } from '../plugin'
import { Plugin, PluginOptions } from '../plugin'
import { ShaclPropertyTemplate } from '../property-template'
import { createTextEditor, Editor } from '../editors'
import mapboxgl from 'mapbox-gl'
import MapboxDraw from '@mapbox/mapbox-gl-draw'
import 'mapbox-gl/dist/mapbox-gl.css'
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'

const dialogTemplate = '<dialog id="shaclMapDialog" onclick="event.target==this && this.close()">\
<div style="position: absolute; z-index: 1; padding: 2px; background-color: #EEEEEECC;">Choose a location by left clicking, then close dialog\
<button id="closeButton" type="button" style="margin-left: 4px; padding: 6px 10px; background: white; border-radius: 4px; border: 0; cursor: pointer;">Close</button>\
</div></dialog>'
const dialogTemplate = '<style>#shaclMapDialog .closeButton { position: absolute; right: 0; top: 0; z-index: 1; padding: 10px 12px; cursor: pointer; border: 0; background-color: #FFFA; }\
#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; }\
</style><dialog id="shaclMapDialog" onclick="event.target==this && this.close()">\
<div class="hint">Draw a polygon or point, then close map</div>\
<button class="closeButton" type="button" onclick="this.parentElement.close()">&#x2715;</button>\
</dialog>'

export class MapBoxPlugin extends Plugin {
map: mapboxgl.Map
dialog: HTMLDialogElement
currentEditor: Editor | undefined
currentMarker: mapboxgl.Marker | undefined
draw: MapboxDraw

constructor(predicate: string, apiKey: string) {
super(predicate)
constructor(options: PluginOptions, apiKey: string) {
super(options)
mapboxgl.accessToken = apiKey

this.dialog = document.querySelector('#shaclMapDialog') as HTMLDialogElement
Expand All @@ -30,6 +36,7 @@ export class MapBoxPlugin extends Plugin {
document.body.style.position = ''
document.body.style.top = ''
window.scrollTo(0, parseInt(scrollY || '0') * -1)
this.setEditorValue()
})
this.map = new mapboxgl.Map({
container: 'shaclMapDialog',
Expand All @@ -38,15 +45,19 @@ export class MapBoxPlugin extends Plugin {
zoom: 5,
center: { lng: 8.657238961696038, lat: 49.87627570549512 }
})
this.map.addControl(new mapboxgl.NavigationControl())
this.map.addControl(new mapboxgl.NavigationControl(), 'top-left')

this.draw = new MapboxDraw({
displayControlsDefault: false,
controls: { point: true, polygon: true },
});
this.map.addControl(this.draw, 'top-left')

this.map.on('idle', () => {
// this fixes wrong size of canvas
this.map.resize()
})
this.map.on('click', (e: mapboxgl.MapLayerTouchEvent) => {
this.setMarker(e.lngLat)
})
this.dialog.querySelector('#closeButton')?.addEventListener('click', () => { this.dialog.close() })
this.map.on('draw.create', () => this.deleteAllButLastDrawing());
}

createInstance(template: ShaclPropertyTemplate, value?: Term): HTMLElement {
Expand All @@ -55,19 +66,26 @@ export class MapBoxPlugin extends Plugin {
button.type = 'button'
button.innerHTML = 'Open map...'
button.style.marginLeft = '5px'
button.classList.add('open-map-button')

button.onclick = () => {
this.currentEditor = instance.querySelector('.editor') as Editor
let markerPos: mapboxgl.LngLat | undefined
const matches = this.currentEditor.value.match(/^POINT\(([+\-]?[0-9]*[.]?[0-9]+),([+\-]?[0-9]*[.]?[0-9]+)\)$/)
if (matches?.length == 3) {
markerPos = { lng: parseFloat(matches[1]), lat: parseFloat(matches[2]) }
}

this.setMarker(markerPos)
if (markerPos) {
this.map.setZoom(15)
this.map.setCenter(markerPos)
this.draw.deleteAll()

const geometry = this.getEditorValue()
if (geometry && geometry.coordinates?.length) {
this.draw.add(geometry)
if (typeof geometry.coordinates[0] === 'number') {
// e.g. Point
this.map.setCenter(geometry.coordinates as mapboxgl.LngLatLike)
this.map.setZoom(15)
} else {
// e.g. Polygon
const bounds = geometry.coordinates[0].reduce((bounds, coord) => {
return bounds.extend(coord as mapboxgl.LngLatLike)
}, new mapboxgl.LngLatBounds(geometry.coordinates[0][0] as mapboxgl.LngLatLike, geometry.coordinates[0][0] as mapboxgl.LngLatLike))
this.map.fitBounds(bounds, { padding: 20, animate: false })
}
} else {
this.map.setZoom(5)
}
Expand All @@ -79,13 +97,57 @@ export class MapBoxPlugin extends Plugin {
return instance
}

setMarker(pos: mapboxgl.LngLat | undefined) {
if (this.currentMarker) {
this.currentMarker.remove()
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)
}
if (this.currentEditor && pos) {
this.currentMarker = new mapboxgl.Marker().setLngLat(pos).addTo(this.map)
this.currentEditor.value = `POINT(${pos.lng},${pos.lat})`
}

getEditorValue() {
if (this.currentEditor) {
const pointCoords = this.currentEditor.value.match(/^POINT\((.*)\)$/)
if (pointCoords?.length == 2) {
const xy = pointCoords[1].split(' ')
if (xy.length === 2) {
return { type: 'Point', coordinates: [parseFloat(xy[0]), parseFloat(xy[1])] }
}
}
const polygonCoords = this.currentEditor.value.match(/^POLYGON[(]{2}(.*)[)]{2}$/)
if (polygonCoords?.length == 2) {
const split = polygonCoords[1].split(',')
if (split.length > 2) {
const coords: number[][][] = []
const outer: number[][] = []
coords.push(outer)
for (const coord of split) {
const xy = coord.split(' ')
if (xy.length === 2) {
outer.push([parseFloat(xy[0]), parseFloat(xy[1])])
}
}
return { type: 'Polygon', coordinates: coords }
}
}
}
return undefined
}

setEditorValue() {
if (this.currentEditor) {
let value = ''
const data = this.draw.getAll()
if (data.features.length) {
const geometry = data.features[0].geometry
if (geometry.coordinates?.length) {
if (geometry.type === 'Point') {
value = `POINT(${geometry.coordinates.join(' ')})`
} else if (geometry.type === 'Polygon') {
value = `POLYGON((${geometry.coordinates[0].map(item => { return item.join(' ') }).join(',')}))`
}
}
}
this.currentEditor.value = value
this.currentEditor.dispatchEvent(new Event('change', { bubbles: true }))
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export function createPropertyInstance(template: ShaclPropertyTemplate, value?:
instance.classList.add('property-instance')
instance.appendChild(new ShaclNode(template.node, template.config, value as NamedNode | BlankNode | undefined, template.label))
} else {
const plugin = template.path ? template.config.plugins[template.path] : undefined
const plugin = template.config.plugins.find(template.path, template.datatype?.value)
if (plugin) {
instance = plugin.createInstance(template, value)
} else {
Expand Down

0 comments on commit 2b30fee

Please sign in to comment.