Skip to content

Commit 9b3d1f6

Browse files
committed
wip: Add the functionnality to edit metadata titles - updated layer schema editor to allow GeoJson import (#461)
1 parent f24db07 commit 9b3d1f6

File tree

1 file changed

+78
-7
lines changed

1 file changed

+78
-7
lines changed

public/layer-json-schema-editor.html

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,52 @@
109109
return { title, properties }
110110
}
111111

112+
// Flatten nested objects using dot notation (mirrors KDK's dotify)
113+
function dotify (obj, prefix, result) {
114+
prefix = prefix || ''
115+
result = result || {}
116+
for (const [key, value] of Object.entries(obj)) {
117+
const fullKey = prefix ? `${prefix}.${key}` : key
118+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
119+
dotify(value, fullKey, result)
120+
} else {
121+
result[fullKey] = value
122+
}
123+
}
124+
return result
125+
}
126+
127+
function getGeoJsonFeatures (geoJson) {
128+
if (geoJson.type === 'FeatureCollection') return geoJson.features || []
129+
if (geoJson.type === 'Feature') return [geoJson]
130+
return []
131+
}
132+
133+
function isGeoJson (obj) {
134+
return obj.type === 'FeatureCollection' || obj.type === 'Feature'
135+
}
136+
137+
// Infer properties from GeoJSON features, mirroring KDK's generatePropertiesSchema
138+
function parseGeoJson (geoJson) {
139+
const features = getGeoJsonFeatures(geoJson)
140+
// Collect first non-null value seen for each key across all features
141+
const propValues = {}
142+
for (const feature of features) {
143+
const props = feature.properties ? dotify(feature.properties) : {}
144+
for (const [key, value] of Object.entries(props)) {
145+
if (propValues[key] !== undefined && propValues[key] !== null) continue
146+
propValues[key] = value
147+
}
148+
}
149+
const properties = []
150+
for (const [key, value] of Object.entries(propValues)) {
151+
let type = typeof value
152+
if (type === 'object' || type === 'undefined') type = 'string'
153+
properties.push({ name: key, type })
154+
}
155+
return properties
156+
}
157+
112158
const App = {
113159
template: `
114160
<q-layout view="hHh lpR fFf">
@@ -143,9 +189,10 @@
143189
>
144190
<q-icon name="upload_file" size="48px" color="grey-6" />
145191
<div class="q-mt-sm text-grey-7">
146-
Drop a JSON schema file here or <strong>click to browse</strong>
192+
Drop a <strong>JSON schema</strong> or <strong>GeoJSON</strong> file here, or <strong>click to browse</strong>
147193
</div>
148-
<input ref="fileInput" type="file" accept=".json" style="display:none" @change="onFileChange" />
194+
<div class="text-caption text-grey-5 q-mt-xs">Schema properties will be induced automatically from GeoJSON feature properties</div>
195+
<input ref="fileInput" type="file" accept=".json,.geojson" style="display:none" @change="onFileChange" />
149196
</div>
150197
<div class="row q-mt-sm q-gutter-sm">
151198
<q-btn outline color="negative" icon="restart_alt" label="Start from scratch" @click="resetSchema" />
@@ -324,6 +371,10 @@
324371
<q-item-section avatar><q-icon name="upload_file" color="primary" /></q-item-section>
325372
<q-item-section>Drop or select an existing Layer JSON schema file to edit it.</q-item-section>
326373
</q-item>
374+
<q-item>
375+
<q-item-section avatar><q-icon name="map" color="primary" /></q-item-section>
376+
<q-item-section>Drop a GeoJSON file to automatically induce the schema from its feature properties.</q-item-section>
377+
</q-item>
327378
<q-item>
328379
<q-item-section avatar><q-icon name="add_circle" color="primary" /></q-item-section>
329380
<q-item-section>Add properties by entering a name, choosing a type (string / number / boolean) and clicking +.</q-item-section>
@@ -426,7 +477,7 @@
426477
$q.notify({ type: 'info', message: 'Schema cleared.' })
427478
}
428479

429-
function loadFromObject (obj) {
480+
function loadFromSchema (obj) {
430481
try {
431482
const parsed = parseSchema(obj)
432483
schemaTitle.value = parsed.title
@@ -438,13 +489,32 @@
438489
}
439490
}
440491

492+
function loadFromGeoJson (obj, filename) {
493+
try {
494+
const inferred = parseGeoJson(obj)
495+
properties.value = inferred
496+
// Suggest a title from filename if none set yet
497+
if (!schemaTitle.value && filename) {
498+
schemaTitle.value = filename.replace(/\.(geo)?json$/i, '')
499+
}
500+
cancelEdit()
501+
$q.notify({ type: 'positive', message: `Induced ${inferred.length} properties from GeoJSON.` })
502+
} catch (e) {
503+
$q.notify({ type: 'negative', message: 'Failed to parse GeoJSON: ' + e.message })
504+
}
505+
}
506+
441507
function readFile (file) {
442508
if (!file) return
443509
const reader = new FileReader()
444510
reader.onload = (e) => {
445511
try {
446512
const obj = JSON.parse(e.target.result)
447-
loadFromObject(obj)
513+
if (isGeoJson(obj)) {
514+
loadFromGeoJson(obj, file.name)
515+
} else {
516+
loadFromSchema(obj)
517+
}
448518
} catch {
449519
$q.notify({ type: 'negative', message: 'Invalid JSON file.' })
450520
}
@@ -460,10 +530,10 @@
460530
function onDrop (event) {
461531
dragging.value = false
462532
const file = event.dataTransfer.files[0]
463-
if (file && file.name.endsWith('.json')) {
533+
if (file && (file.name.endsWith('.json') || file.name.endsWith('.geojson'))) {
464534
readFile(file)
465535
} else {
466-
$q.notify({ type: 'warning', message: 'Please drop a .json file.' })
536+
$q.notify({ type: 'warning', message: 'Please drop a .json or .geojson file.' })
467537
}
468538
}
469539

@@ -498,7 +568,8 @@
498568
typeOptions, dragging, showHelp, editingIdx, editingName,
499569
schemaPreview, canExport,
500570
addProperty, removeProperty, startEdit, saveEdit, cancelEdit,
501-
resetSchema, onFileChange, onDrop, exportSchema, copySchema
571+
resetSchema, onFileChange, onDrop, exportSchema, copySchema,
572+
loadFromGeoJson
502573
}
503574
}
504575
}

0 commit comments

Comments
 (0)