Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added format handler for different geometry types for jsonform #1276

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
180306b
feat: added initial bounding box format handler for jsonform
santilland Sep 25, 2024
c5d7aaf
feat: add polygons and editors
A-Behairi Oct 9, 2024
2dfe5c7
chore: cleanup
A-Behairi Oct 9, 2024
5e46728
Merge remote-tracking branch 'origin/main' into jsonform/feature/draw…
A-Behairi Oct 10, 2024
1725bff
Merge remote-tracking branch 'origin/main' into jsonform/feature/draw…
A-Behairi Oct 22, 2024
7f3a774
feat: selection input + adjusted returned values accordingly
A-Behairi Oct 22, 2024
6bf7375
fix: feature selection story & renamed drawtool to bbox
A-Behairi Oct 22, 2024
ca762fa
chore: format
A-Behairi Oct 22, 2024
e20e4ec
fix: lint & tests
A-Behairi Oct 22, 2024
2614e1c
fix: spread features
A-Behairi Oct 25, 2024
e3f7a60
Merge remote-tracking branch 'origin/main' into jsonform/feature/draw…
A-Behairi Oct 29, 2024
029e3fa
feat: type spatial custom validator
A-Behairi Oct 31, 2024
5b6afce
chore: update collection schema
A-Behairi Nov 7, 2024
c706f7f
test: render drawtools on type spatial
A-Behairi Nov 7, 2024
44cec24
Merge remote-tracking branch 'origin/main' into jsonform/feature/draw…
A-Behairi Nov 7, 2024
512e2d0
Merge remote-tracking branch 'origin/main' into jsonform/feature/draw…
A-Behairi Nov 7, 2024
cf8599b
feat: add projection option
A-Behairi Nov 7, 2024
c291d3e
Merge remote-tracking branch 'origin/main' into jsonform/feature/draw…
A-Behairi Nov 7, 2024
8985256
test: projection
A-Behairi Nov 7, 2024
1d9f22a
fix: auto start drawing
A-Behairi Nov 22, 2024
f7902b1
fix: remove `*-editor` formats
A-Behairi Nov 22, 2024
ddf3459
fix: update editors types & adapt the validator and stories
A-Behairi Nov 22, 2024
ac976e7
feat: support format point/points
A-Behairi Nov 26, 2024
1668a63
Merge remote-tracking branch 'origin/main' into jsonform/feature/draw…
A-Behairi Nov 27, 2024
4af41c0
feat: support wkt and geojson types and handle their validations
A-Behairi Nov 27, 2024
33e2c36
chore: update stories and clean up
A-Behairi Nov 27, 2024
8f7d8ea
test: adjust tests
A-Behairi Nov 27, 2024
09e5f5e
fix: return a feature for single formats and feature collection for p…
A-Behairi Nov 29, 2024
76f93d5
feat: support line/lines format
A-Behairi Nov 29, 2024
c6abbd9
fix: discard drawing on destroy
A-Behairi Dec 2, 2024
3af6909
fix: adjust collection schema and format
A-Behairi Dec 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions elements/jsonform/src/custom-inputs/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { JSONEditor } from "@json-editor/json-editor/src/core.js";
import { MinMaxEditor } from "./minmax";
import { SpatialEditor, spatialValidator } from "./spatial";

// Define custom input types
const inputs = [
Expand All @@ -8,6 +9,56 @@ const inputs = [
format: "minmax",
func: MinMaxEditor,
},
{
type: "spatial",
format: "bounding-boxes",
func: SpatialEditor,
},
{
type: "spatial",
format: "bounding-box",
func: SpatialEditor,
},
{
type: "spatial",
format: "bounding-boxes-editor",
func: SpatialEditor,
},
{
type: "spatial",
format: "bounding-box-editor",
func: SpatialEditor,
},
{
type: "spatial",
format: "polygons",
func: SpatialEditor,
},
{
type: "spatial",
format: "polygon",
func: SpatialEditor,
},
{
type: "spatial",
format: "polygons-editor",
func: SpatialEditor,
},
{
type: "spatial",
format: "polygon-editor",
func: SpatialEditor,
},
{
type: "spatial",
format: "feature",
func: SpatialEditor,
},
{
type: "spatial",
format: "features",
func: SpatialEditor,
},
];

/**
Expand All @@ -16,6 +67,9 @@ const inputs = [
* @param {{[key: string]: any}} startVals - Initial values for the custom inputs
*/
export const addCustomInputs = (startVals) => {
// Add custom validators for spatial inputs
JSONEditor.defaults["custom_validators"].push(spatialValidator);

// Iterate over each custom input definition
inputs.map(({ type, format, func }) => {
JSONEditor.defaults["startVals"] = startVals;
Expand Down
177 changes: 177 additions & 0 deletions elements/jsonform/src/custom-inputs/spatial/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { AbstractEditor } from "@json-editor/json-editor/src/editor.js";
import { isBox, isMulti, isPolygon, isSelection, setAttributes } from "./utils";
// import "@eox/drawtools";

// Define a custom editor class extending AbstractEditor
export class SpatialEditor extends AbstractEditor {
register() {
super.register();
}

unregister() {
super.unregister();
}

// Build the editor UI
build() {
// const properties = this.schema.properties;
const options = this.options;
const description = this.schema.description;
const theme = this.theme;
// const startVals = this.defaults.startVals[this.key];

// Create label and description elements if not in compact mode
if (!options.compact)
this.header = this.label = theme.getFormInputLabel(
this.getTitle(),
this.isRequired(),
);
if (description)
this.description = theme.getFormInputDescription(
this.translateProperty(description),
);
if (options.infoText)
this.infoButton = theme.getInfoButton(
this.translateProperty(options.infoText),
);

const drawtoolsEl = document.createElement("eox-drawtools");

const enableEditor = this.schema.format.includes("editor");

const drawType = isPolygon(this.schema) ? "Polygon" : "Box";
const attributes = {
type: drawType,
};

if (this.schema?.options?.projection) {
attributes.projection = this.schema.options.projection;
}

if (isSelection(this.schema)) {
attributes["layer-id"] = this.schema.options.layerId;
}
if (isMulti(this.schema)) {
attributes["multiple-features"] = true;
}

if (enableEditor) {
attributes["import-features"] = true;
attributes["show-editor"] = true;
}

if (isMulti(this.schema)) {
attributes["show-list"] = true;
}

if ("for" in (this.schema.options ?? {})) {
attributes.for = this.options.for;
} else {
// We need to create a map
const eoxmapEl = document.createElement("eox-map");
eoxmapEl.projection = "EPSG:4326";
const mapId = "map-" + this.formname.replace(/[^\w\s]/gi, "");
eoxmapEl.layers = [{ type: "Tile", source: { type: "OSM" } }];

setAttributes(eoxmapEl, {
id: mapId,
style: "width: 100%; height: 300px;",
});
this.container.appendChild(eoxmapEl);
attributes.for = "eox-map#" + mapId;
}
setAttributes(drawtoolsEl, attributes);

this.input = drawtoolsEl;
this.input.id = this.formname;
this.control = theme.getFormControl(
this.label,
this.input,
this.description,
this.infoButton,
);

if (this.schema.readOnly || this.schema.readonly) {
this.disable(true);
this.input.disabled = true;
}

const featureProperty = this.schema?.options?.featureProperty;

/**
* Ensures that features of length 1 are not returned as an array
*
* @param {import("ol/Feature").default|import("ol/Feature").default[]} features
* @param {(feature:import("ol/Feature").default)=>any} callback
*/
const spreadFeatures = (features, callback) => {
if (features.length) {
if (!isMulti(this.schema) && features.length === 1) {
return callback(features[0]);
}
return features.map(callback);
} else {
return callback(features);
}
};

// Add event listener for change events on the draw tools
this.input.addEventListener(
"drawupdate",
/** @type {EventListener} */ (
/**
* @param {CustomEvent<import("ol/Feature").default|import("ol/Feature").default[]>} e
*/
(e) => {
e.preventDefault();
e.stopPropagation();

switch (true) {
case !e.detail || e.detail?.length === 0: {
this.value = null;
break;
}
case isSelection(this.schema): {
/** @param {import("ol/Feature").default} feature */
const getProperty = (feature) =>
featureProperty
? (feature.get(featureProperty) ?? feature)
: feature;

this.value = spreadFeatures(e.detail, getProperty);
break;
}
case isBox(this.schema): {
/** @param {import("ol/Feature").default} feature */
const getExtent = (feature) => feature.getGeometry().getExtent();
this.value = spreadFeatures(e.detail, getExtent);
break;
}
case isPolygon(this.schema):
this.value = spreadFeatures(e.detail, (feature) => feature);
break;
default:
break;
}

this.onChange(true);
}
),
);

this.container.appendChild(this.control);
}

// Destroy the editor and remove all associated elements
destroy() {
if (this.label && this.label.parentNode)
this.label.parentNode.removeChild(this.label);
if (this.description && this.description.parentNode)
this.description.parentNode.removeChild(this.description);
if (this.input && this.input.parentNode) {
this.input.parentNode.removeChild(this.input);
this.input.remove();
}
super.destroy();
}
}
2 changes: 2 additions & 0 deletions elements/jsonform/src/custom-inputs/spatial/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { SpatialEditor } from "./editor";
export { default as spatialValidator } from "./validator";
75 changes: 75 additions & 0 deletions elements/jsonform/src/custom-inputs/spatial/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Whether a schema has feature/feature format or not
*/
export const isSelection = (schema) =>
["feature", "features"].some((f) => schema?.format?.includes(f));

/**
* Whether a schema has ploygon/polygons format or not
*/
export const isPolygon = (schema) =>
["polygon", "polygons"].some((p) => schema?.format?.includes(p));

/**
* Whether a schema has bbox/bboxes format or not
*/
export const isBox = (schema) =>
["bounding-boxes", "bounding-box"].some((p) => schema?.format?.includes(p));

/**
* Whether a schema expects multiple values not
*/
export const isMulti = (schema) =>
["bounding-boxes", "polygons", "features"].some((m) =>
schema?.format?.includes(m),
);

/**
* Whether a schema is supported by the spatial editor
**/
export const isSupported = (schema) =>
isSelection(schema) || isPolygon(schema) || isBox(schema);

/**
* Set multiple attributes to an element
*
* @param {Element} element - The DOM element to set attributes on
* @param {{[key: string]: any}} attributes - The attributes to set on the element
*/
export function setAttributes(element, attributes) {
Object.keys(attributes).forEach((attr) => {
element.setAttribute(attr, attributes[attr]);
});
}

/**
* Check if a value satisfies a given type
* supported types: "string", "number", "boolean", "array", "object"
*
* @param {*} val
* @param {string} type
* @returns {boolean}
*/
export const satisfiesType = (val, type) => {
if (!val || !type) {
return false;
}

switch (type) {
case "string":
return typeof val === "string";

case "number":
return !isNaN(val);

case "boolean":
return typeof val === "boolean";

case "array":
return Array.isArray(val);

case "object":
return typeof val === "object" && !!Object.keys(val).length;
}
return false;
};
Loading
Loading