/**
### /ui/elements/drawing
Dictionary entries:
- draw_dialog_title
- draw_dialog_begin_drawing
- draw_dialog_cancel_drawing
- draw_dialog_remove_vertex
- draw_dialog_save
- draw_dialog_save_single
- draw_point
- draw_position
- draw_polygon
- draw_rectangle
- circle_config
- draw_circle
- draw_circle_2pt
- radius
- units
- draw_line
- create
@requires /dictionary
@module /ui/elements/drawing
*/
export default {
point,
line,
polygon,
rectangle,
circle_2pt,
circle,
locator,
drawOnclick,
};
/**
@function drawOnclick
@description
The drawOnClick method is triggered by clicking on a drawing element button.
The 'active' class is toggled on the button element. The drawing interaction is finished if the 'active' class is toggled off.
A callback method is assigned to the interaction before the interaction object is passed as argument to call the mapview's draw interaction.
@param {Object} e The click event.
@param {layer} layer Decorated Mapp Layer.
@param {Object} interaction Mapview drawing interaction.
@property {Object} e.target The click event target [button].
*/
function drawOnclick(e, layer, interaction) {
const btn = e.target;
if (!btn.classList.toggle('active')) {
layer.mapview.interaction.finish();
return;
}
!layer.display && layer.show();
interaction.callback ??= (feature) => {
mapp.location.create(feature, interaction, layer);
btn.classList.remove('active');
delete layer.mapview.interaction;
mapp.ui.elements.helpDialog();
// Set highlight interaction if no other interaction is current after 400ms.
setTimeout(() => {
!layer.mapview.interaction && layer.mapview.interactions.highlight();
}, 400);
};
layer.mapview.interactions.draw(interaction);
interaction.helpDialog.header = mapp.utils
.html`<h3 style="line-height: 2em; margin-right: 1em">${mapp.dictionary.draw_dialog_title}</h3>`;
interaction.helpDialog.data_id = 'dialog_drawing';
mapp.ui.elements.helpDialog(interaction.helpDialog);
btn.classList.add('active');
}
/**
@function point
@description Creates a button for drawing a point on the map.
@param {layer} layer Decorated MAPP Layer.
@return {HTMLElement} The button element.
*/
function point(layer) {
const helpDialog = {
content: mapp.utils.html.node`<li>
<ul>${mapp.dictionary.draw_dialog_begin_drawing}</ul>
<ul>${mapp.dictionary.draw_dialog_save_single}</ul>`,
};
// Set the default values
layer.draw.point = {
layer,
label: mapp.dictionary.draw_point,
helpDialog,
type: 'Point',
...layer.draw.point,
};
// Create the button
layer.draw.point.btn = mapp.utils.html.node`
<button
class="flat wide bold"
onclick=${(e) => drawOnclick(e, layer, layer.draw.point)}>
${layer.draw.point.label}`;
return layer.draw.point.btn;
}
/**
@function line
@description Creates a button for drawing a line on the map.
@param {layer} layer Decorated MAPP Layer.
@return {HTMLElement} The button element.
*/
function line(layer) {
const helpDialog = {
content: mapp.utils.html.node`<li>
<ul>${mapp.dictionary.draw_dialog_begin_drawing}</ul>
<ul>${mapp.dictionary.draw_dialog_remove_vertex}</ul>
<ul>${mapp.dictionary.draw_dialog_save_single}</ul>`,
};
// Set the default values
layer.draw.line = {
layer,
label: mapp.dictionary.draw_line,
helpDialog,
type: 'LineString',
...layer.draw.line,
};
// Create the button
layer.draw.line.btn = mapp.utils.html.node`
<button
class="flat wide bold"
onclick=${(e) => drawOnclick(e, layer, layer.draw.line)}>
${layer.draw.line.label}`;
return layer.draw.line.btn;
}
/**
@function polygon
@description Creates a button for drawing a polygon on the map.
@param {layer} layer Decorated MAPP Layer.
@return {HTMLElement} The button element.
*/
function polygon(layer) {
const helpDialog = {
content: mapp.utils.html.node`<li>
<ul>${mapp.dictionary.draw_dialog_begin_drawing}</ul>
<ul>${mapp.dictionary.draw_dialog_cancel_drawing}
<ul>${mapp.dictionary.draw_dialog_remove_vertex}</ul>
<ul>${mapp.dictionary.draw_dialog_save}</ul>`,
};
// Set the default values
layer.draw.polygon = {
layer,
label: mapp.dictionary.draw_polygon,
helpDialog,
type: 'Polygon',
...layer.draw.polygon,
};
// Create the button
layer.draw.polygon.btn = mapp.utils.html.node`
<button
class="flat wide bold"
onclick=${(e) => drawOnclick(e, layer, layer.draw.polygon)}>
${layer.draw.polygon.label}`;
return layer.draw.polygon.btn;
}
/**
@function rectangle
@description Creates a button for rectangle a line on the map.
@param {layer} layer Decorated MAPP Layer.
@return {HTMLElement} The button element.
*/
function rectangle(layer) {
const helpDialog = {
content: mapp.utils.html.node`<li>
<ul>${mapp.dictionary.draw_dialog_begin_drawing}</ul>
<ul>${mapp.dictionary.draw_dialog_save_single}</ul>`,
};
// Set the default values
layer.draw.rectangle = {
layer,
label: mapp.dictionary.draw_rectangle,
helpDialog,
type: 'Circle',
geometryFunction: ol.interaction.Draw.createBox(),
...layer.draw.rectangle,
};
// Create the button
layer.draw.rectangle.btn = mapp.utils.html.node`
<button
class="flat wide bold"
onclick=${(e) => drawOnclick(e, layer, layer.draw.rectangle)}>
${layer.draw.rectangle.label}`;
return layer.draw.rectangle.btn;
}
/**
@function circle_2pt
@description Creates a button for circle_2pt a line on the map.
@param {layer} layer Decorated MAPP Layer.
@return {HTMLElement} The button element.
*/
function circle_2pt(layer) {
const helpDialog = {
content: mapp.utils.html.node`<li>
<ul>${mapp.dictionary.draw_dialog_begin_drawing}</ul>
<ul>${mapp.dictionary.draw_dialog_save_single}</ul>`,
};
// Set the default values
layer.draw.circle_2pt = {
layer,
type: 'Circle',
helpDialog,
geometryFunction: ol.interaction.Draw.createRegularPolygon(33),
label: mapp.dictionary.draw_circle_2pt,
...layer.draw.circle_2pt,
};
// Create the button
layer.draw.circle_2pt.btn = mapp.utils.html.node`
<button
class="flat wide bold"
onclick=${(e) => drawOnclick(e, layer, layer.draw.circle_2pt)}>
${layer.draw.circle_2pt.label}`;
return layer.draw.circle_2pt.btn;
}
/**
@function circle
@description Creates a button for circle a line on the map.
@param {layer} layer Decorated MAPP Layer.
@return {HTMLElement} The button element.
*/
function circle(layer) {
const helpDialog = {
content: mapp.utils.html.node`<li>
<ul>${mapp.dictionary.draw_dialog_begin_drawing}</ul>
<ul>${mapp.dictionary.draw_dialog_save_single}</ul>`,
};
// Set the default values
layer.draw.circle = {
layer,
helpDialog,
type: 'Point',
units: 'meter',
radius: 100,
radiusMin: 1,
radiusMax: 1000,
// Methods to transform input radius.
unitConversion: {
meter: (v) => v,
km: (v) => v * 1000,
miles: (v) => v * 1609.34,
meter2: (v) => Math.sqrt(v / Math.PI),
km2: (v) => Math.sqrt((v * 1000000) / Math.PI),
},
geometryFunction: (coordinates) => {
const polygonCircular = new ol.geom.Polygon.circular(
ol.proj.toLonLat(coordinates),
layer.draw.circle.unitConversion[layer.draw.circle.units](
layer.draw.circle.radius,
),
64,
);
return polygonCircular.transform('EPSG:4326', 'EPSG:3857');
},
label: mapp.dictionary.draw_circle,
...layer.draw.circle,
};
// Build an array for the unit, label, min and max values.
const units = [
{
option: 'meter',
title: 'Meter',
min: 1,
max: 1000,
},
{
option: 'km',
title: 'KM',
min: 1,
max: 10,
},
{
option: 'miles',
title: 'Miles',
min: 1,
max: 10,
},
{
option: 'meter2',
title: 'Meter²',
min: 1,
max: 1000,
},
{
option: 'km2',
title: 'KM²',
min: 1,
max: 10,
},
];
const unitsDropDown = mapp.utils.html.node`
<div style="display: grid; grid-template-columns: 100px 1fr; align-items: center;">
<div style="grid-column: 1;">${mapp.dictionary.units}</div>
<div style="grid-column: 2;">
${mapp.ui.elements.dropdown({
placeholder: units.find(
(entry) => entry.option === layer.draw.circle.units,
).title,
entries: units,
callback: (e, entry) => {
layer.draw.circle.units = entry.option;
layer.draw.circle.radiusMin = entry.min;
layer.draw.circle.radiusMax = entry.max;
// Update the value of the slider to ensure it is within the new min and max values.
layer.draw.circle.radius =
layer.draw.circle.radius > layer.draw.circle.radiusMax
? layer.draw.circle.radiusMax
: layer.draw.circle.radius;
// Render the slider after changes
createSlider();
},
})}`;
const rangeSlider = mapp.utils.html.node`<div>`;
createSlider();
/**
@function createSlider
@description Creates a slider for the circle radius, renders this slider into the rangeSlider div, and assigns a callback function to update the radius value.
*/
function createSlider() {
mapp.utils.render(
rangeSlider,
mapp.ui.elements.slider({
min: layer.draw.circle.radiusMin,
max: layer.draw.circle.radiusMax,
val: layer.draw.circle.radius,
callback: (e) => {
layer.draw.circle.radius = parseFloat(e);
},
}),
);
}
layer.draw.circle.panel = mapp.utils.html.node`
<div class="panel flex-col">
${unitsDropDown}
${rangeSlider}`;
layer.draw.circle.btn = mapp.utils.html.node`
<button
class="flat wide bold"
onclick=${(e) => drawOnclick(e, layer, layer.draw.circle)}>
${layer.draw.circle.label}`;
// The config elements are not shown.
if (layer.draw.circle.hidePanel) return layer.draw.circle.btn;
// Return the config element in a drawer with the interaction toggle button as sibling.
return mapp.utils.html.node`<div>
${mapp.ui.elements.drawer({
header: mapp.utils.html`
<h3>${mapp.dictionary.circle_config}</h3>
<div class="material-symbols-outlined caret"/>`,
content: layer.draw.circle.panel,
})}${layer.draw.circle.btn}`;
}
/**
@function locator
@description Creates a button for drawing a point at your current location.
@param {layer} layer Decorated MAPP Layer.
@return {HTMLElement} The button element.
*/
function locator(layer) {
layer.draw.locator = {
layer,
label: mapp.dictionary.draw_position,
type: 'Point',
...layer.draw.locator,
};
layer.draw.locator.btn = mapp.utils.html.node`
<button
class="flat wide bold"
onclick=${(e) => {
mapp.utils.getCurrentPosition(async (pos) => {
const location = {
layer: layer,
table: layer.tableCurrent(),
new: true,
};
const coords = ol.proj.transform(
[parseFloat(pos.coords.longitude), parseFloat(pos.coords.latitude)],
'EPSG:4326',
`EPSG:${layer.srid}`,
);
location.id = await mapp.utils.xhr({
method: 'POST',
url:
`${layer.mapview.host}/api/query?` +
mapp.utils.paramString({
template: 'location_new',
locale: layer.mapview.locale.key,
layer: layer.key,
table: location.table,
}),
body: JSON.stringify({
[layer.geom]: {
type: 'Point',
coordinates: coords,
},
}),
});
mapp.location.get(location);
});
}}>${layer.draw.locator.label}`;
return layer.draw.locator.btn;
}