diff --git a/css/svg/material-icons.svg b/css/svg/material-icons.svg
index 50d26a394..9cc5d6245 100644
--- a/css/svg/material-icons.svg
+++ b/css/svg/material-icons.svg
@@ -59,5 +59,7 @@
+
+
diff --git a/package-lock.json b/package-lock.json
index 71d7dfc90..5bc78805c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1810,9 +1810,9 @@
"dev": true
},
"electron-to-chromium": {
- "version": "1.4.329",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.329.tgz",
- "integrity": "sha512-dcwPzNUG4+reo5z+wHnrl2eZMu4kz+nLQEeepxLEDTLDC7Mi7AVTM4NXWct1TZyu3G4oQgygaAfbByaBtPqw2Q==",
+ "version": "1.4.332",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.332.tgz",
+ "integrity": "sha512-c1Vbv5tuUlBFp0mb3mCIjw+REEsgthRgNE8BlbEDKmvzb8rxjcVki6OkQP83vLN34s0XCxpSkq7AZNep1a6xhw==",
"dev": true
},
"elm-pep": {
@@ -6463,9 +6463,9 @@
"integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA=="
},
"webpack": {
- "version": "5.76.0",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz",
- "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==",
+ "version": "5.76.2",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz",
+ "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.3",
diff --git a/package.json b/package.json
index 781945930..259dd7ca8 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,7 @@
"run-sequence": "^2.2.1",
"source-map-loader": "^3.0.0",
"terser-webpack-plugin": "^5.2.4",
- "webpack": "^5.76.0",
+ "webpack": "^5.76.2",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.3",
diff --git a/scss/_draw.scss b/scss/_draw.scss
new file mode 100644
index 000000000..d36b8bf28
--- /dev/null
+++ b/scss/_draw.scss
@@ -0,0 +1,56 @@
+.o-draw-toolbar {
+ bottom: 0;
+ left: 50%;
+ margin-bottom: .5rem !important;
+ max-width: 80%;
+ position: relative;
+ -ms-transform: translate(-50%, 0);
+ -webkit-transform: translate(-50%, 0);
+ transform: translate(-50%, 0);
+ z-index: 10;
+}
+
+.o-draw-stylewindow {
+ font-family: Arial,sans-serif;
+ width: 250px;
+ position:absolute;
+ right: 1rem;
+ -ms-transform: translate(0, -50%);
+ -webkit-transform: translate(0, -50%);
+ transform: translate(0, -50%);
+ top:50%;
+ height: calc(100% - 9rem);
+}
+
+.o-draw-stylewindow input[type="radio"] {
+ display:none;
+}
+
+.o-draw-stylewindow input[type="radio"] + label span {
+ display:inline-block;
+ width:24px;
+ height:24px;
+ vertical-align:middle;
+ cursor:pointer;
+ -moz-border-radius: 50%;
+ border-radius: 50%;
+ box-shadow: 0 0 0 2px white, 0 0 0 4px white;
+}
+
+.o-draw-stylewindow input[type="radio"]:checked + label span{
+ box-shadow: 0 0 0 2px white, 0 0 0 4px #ababab;
+}
+
+.o-draw-stylewindow input[type="radio"] ~ label span:hover {
+ box-shadow: 0 0 0 2px white, 0 0 0 4px #cdcdcd;
+}
+
+.o-draw-stylewindow input[type="radio"] ~ label {
+ display:inline-block;
+ vertical-align:middle;
+ padding:5px;
+}
+
+.o-draw-stylewindow ul li {
+ float:left;
+}
diff --git a/scss/origo.scss b/scss/origo.scss
index 9c84b55b6..adef8d848 100644
--- a/scss/origo.scss
+++ b/scss/origo.scss
@@ -32,6 +32,7 @@
@import 'viewer';
@import 'rotate';
@import 'mapmenu';
+ @import 'draw';
@import 'editor';
@import 'editor-toolbar';
@import 'position';
diff --git a/scss/ui/_flex.scss b/scss/ui/_flex.scss
index 7d27913fa..feeba75f0 100644
--- a/scss/ui/_flex.scss
+++ b/scss/ui/_flex.scss
@@ -114,4 +114,8 @@ $flex-grow-all: 2;
.basis-100 {
flex-basis: 100%;
}
+
+ .small-gap {
+ gap: 0.2rem;
+ }
}
diff --git a/src/controls.js b/src/controls.js
index 7be970928..590bb335d 100644
--- a/src/controls.js
+++ b/src/controls.js
@@ -2,6 +2,7 @@ export { default as About } from './controls/about';
export { default as Attribution } from './controls/attribution';
export { default as Bookmarks } from './controls/bookmarks';
export { default as Draganddrop } from './controls/draganddrop';
+export { default as Draw } from './controls/draw';
export { default as Editor } from './controls/editor';
export { default as Fullscreen } from './controls/fullscreen';
export { default as Geoposition } from './controls/geoposition';
diff --git a/src/controls/draganddrop.js b/src/controls/draganddrop.js
index ae0e0fef1..919cb25f7 100644
--- a/src/controls/draganddrop.js
+++ b/src/controls/draganddrop.js
@@ -4,11 +4,8 @@ import GeoJSONFormat from 'ol/format/GeoJSON';
import IGCFormat from 'ol/format/IGC';
import KMLFormat from 'ol/format/KML';
import TopoJSONFormat from 'ol/format/TopoJSON';
-import VectorSource from 'ol/source/Vector';
-import VectorLayer from 'ol/layer/Vector';
import Style from '../style';
import { Component, InputFile, Button, Element as El } from '../ui';
-import { getStylewindowStyle } from './editor/stylewindow';
const DragAndDrop = function DragAndDrop(options = {}) {
let dragAndDrop;
@@ -118,8 +115,6 @@ const DragAndDrop = function DragAndDrop(options = {}) {
}
}]
};
- let vectorSource;
- let vectorLayer;
const vectorStyles = Style.createGeometryStyle(featureStyles);
dragAndDrop = new olDragAndDrop({
formatConstructors: [
@@ -147,30 +142,26 @@ const DragAndDrop = function DragAndDrop(options = {}) {
i += 1;
}
}
- vectorSource = new VectorSource({
- features: event.features
- });
- vectorSource.forEachFeature((feature) => {
- if (feature.get('style') && styleByAttribute) {
- const featureStyle = getStylewindowStyle(feature, feature.get('style'));
- feature.setStyle(featureStyle);
- }
- });
if (!viewer.getGroup(groupName)) {
viewer.addGroup({ title: groupTitle, name: groupName, expanded: true, draggable });
}
- vectorLayer = new VectorLayer({
- source: vectorSource,
- name: layerName,
+ const layerOptions = {
group: groupName,
+ name: layerName,
title: layerTitle,
+ zIndex: 6,
+ styleByAttribute,
queryable: true,
removable: true,
- style: vectorStyles[event.features[0].getGeometry().getType()]
- });
-
- map.addLayer(vectorLayer);
- map.getView().fit(vectorSource.getExtent());
+ visible: true,
+ source: 'none',
+ type: 'GEOJSON',
+ features: event.features
+ };
+ if (!styleByAttribute) {
+ layerOptions.style = vectorStyles[event.features[0].getGeometry().getType()];
+ }
+ viewer.addLayer(layerOptions);
});
this.render();
},
diff --git a/src/controls/draw.js b/src/controls/draw.js
new file mode 100644
index 000000000..bb7e99468
--- /dev/null
+++ b/src/controls/draw.js
@@ -0,0 +1,674 @@
+import { Button, dom, Component, Element as El, Input, InputFile, Modal } from '../ui';
+import DrawHandler from './draw/drawhandler';
+import drawExtraTools from './draw/drawtools';
+import exportToFile from '../utils/exporttofile';
+import validate from '../utils/validate';
+
+const Draw = function Draw(options = {}) {
+ const {
+ buttonText = 'Rita',
+ placement = ['menu'],
+ icon = '#fa-pencil',
+ annotation,
+ showAttributeButton = false,
+ showDownloadButton = false,
+ showSaveButton = false,
+ multipleLayers = false
+ } = options;
+
+ let {
+ isActive = false
+ } = options;
+
+ const drawDefaults = {
+ layerTitle: 'Ritlager',
+ groupName: 'none',
+ groupTitle: 'Ritlager',
+ visible: true,
+ styleByAttribute: true,
+ queryable: false,
+ removable: true,
+ exportable: true,
+ drawlayer: true,
+ draggable: true
+ };
+
+ let map;
+ let viewer;
+ let drawTools;
+ let mapTools;
+ let screenButtonContainer;
+ let screenButton;
+ let mapMenu;
+ let menuItem;
+ let stylewindow;
+ let saveButton;
+ let layerAttributeButton;
+ let thisComponent;
+ let drawHandler;
+
+ const drawOptions = Object.assign({}, drawDefaults, options);
+
+ function setActive(state) {
+ if (state === true) {
+ document.getElementById(thisComponent.getId()).classList.remove('o-hidden');
+ if (screenButton) {
+ screenButton.setState('active');
+ }
+ isActive = true;
+ } else {
+ document.getElementById(thisComponent.getId()).classList.add('o-hidden');
+ thisComponent.dispatch('toggleDraw', { tool: 'cancel' });
+ stylewindow.dispatch('showStylewindow', false);
+ if (screenButton) {
+ screenButton.setState('initial');
+ }
+ isActive = false;
+ }
+ }
+
+ function onEnableInteraction(e) {
+ if (e.detail.name === 'draw' && e.detail.active) {
+ setActive(true);
+ } else {
+ setActive(false);
+ }
+ }
+
+ function toggleState(tool, state) {
+ if (state === false) {
+ tool.setState('initial');
+ } else {
+ tool.setState('active');
+ }
+ }
+
+ function changeDrawState(detail) {
+ const tools = Object.getOwnPropertyNames(drawTools);
+ tools.forEach((tool) => {
+ if (tool === detail.tool) {
+ toggleState(drawTools[tool], detail.active);
+ } else {
+ toggleState(drawTools[tool], false);
+ }
+ });
+ }
+
+ const attributeForm = Component({ // Add attribute to feature
+ name: 'attributeForm',
+ show() {
+ if (drawHandler.getSelection().getArray().length > 0) {
+ const feature = drawHandler.getSelection().getArray()[0];
+ const val = feature.get('popuptext') || '';
+
+ const formSaveButton = Button({
+ cls: 'margin-small icon-smaller light box-shadow',
+ style: 'border-radius: 3px',
+ icon: '#ic_save_24px',
+ text: 'Spara'
+ });
+
+ const cancelButton = Button({
+ cls: 'margin-small icon-smaller light box-shadow',
+ style: 'border-radius: 3px',
+ icon: '#ic_close_24px',
+ text: 'Avbryt'
+ });
+
+ const inputEl = Input({
+ cls: 'no-margin',
+ placeholderText: 'Ange popuptext',
+ value: val
+ });
+
+ const modalContent = El({
+ cls: 'padding-small flex row wrap align-start justify-space-evenly',
+ components: [inputEl, formSaveButton, cancelButton]
+ });
+
+ const modal = Modal({
+ title: 'Popuptext',
+ contentCmp: modalContent,
+ target: viewer.getId()
+ });
+
+ formSaveButton.on('click', () => {
+ const inputVal = inputEl.getValue() || '';
+ feature.set('popuptext', inputVal);
+ modal.closeModal();
+ });
+
+ cancelButton.on('click', () => {
+ modal.closeModal();
+ });
+
+ modal.show();
+ }
+ }
+ });
+
+ const onLayerDelete = function onLayerDelete(evt) {
+ const activeLayer = drawHandler.getActiveLayer();
+ const removedLayer = evt.element;
+ if (activeLayer === removedLayer) {
+ const drawLayers = drawHandler.getDrawLayers();
+ if (drawLayers.length > 0) {
+ drawHandler.setActiveLayer(drawLayers[drawLayers.length - 1]);
+ } else {
+ drawHandler.setActiveLayer(null);
+ }
+ }
+ };
+
+ const layerForm = Component({ // Handle draw layers
+ name: 'layerForm',
+ show() {
+ const drawLayers = drawHandler.getDrawLayers();
+ const activeLayer = drawHandler.getActiveLayer();
+ const components = [];
+ let modal;
+ const thisForm = this;
+
+ drawLayers.reverse().forEach(drawLayer => {
+ const layerTitle = drawLayer.get('title');
+ const layerName = drawLayer.get('name');
+
+ const inputEl = Input({
+ cls: 'margin-right',
+ placeholderText: 'Ange lagernamn',
+ value: layerTitle
+ });
+
+ inputEl.on('focusout', (e) => {
+ drawLayer.set('title', e.value);
+ });
+
+ const activeButton = Button({
+ cls: 'margin-right-small padding-small icon-smaller round light box-shadow relative o-tooltip',
+ icon: '#ic_check_24px',
+ state: drawLayer === activeLayer ? 'active' : 'initial',
+ click() {
+ drawHandler.setActiveLayer(drawLayer);
+ thisForm.dispatch('activeLayerChange', { layername: layerName });
+ },
+ tooltipText: 'Aktivera ritlager',
+ tooltipPlacement: 'west'
+ });
+
+ this.on('activeLayerChange', (e) => {
+ activeButton.setState(e.layername === layerName ? 'active' : 'initial');
+ });
+
+ const deleteButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative o-tooltip',
+ icon: '#ic_delete_24px',
+ async click() {
+ if (window.confirm('Vill du radera det här ritlagret?') === true) {
+ await map.removeLayer(drawLayer);
+ modal.closeModal();
+ thisForm.show();
+ }
+ },
+ tooltipText: 'Radera ritlager',
+ tooltipPlacement: 'west'
+ });
+
+ const downloadLayerButton = Button({
+ cls: 'margin-right-small padding-small icon-smaller round light box-shadow relative o-tooltip',
+ icon: '#ic_download_24px',
+ click() {
+ const features = drawLayer.getSource().getFeatures();
+ exportToFile(features, 'geojson', {
+ featureProjection: viewer.getProjection().getCode(),
+ filename: drawLayer.get('title') || 'export'
+ });
+ },
+ tooltipText: 'Ladda ner ritlager',
+ tooltipPlacement: 'west'
+ });
+
+ const layerRow = El({
+ cls: 'flex row align-start justify-space-evenly',
+ components: [inputEl, activeButton, downloadLayerButton, deleteButton],
+ tagName: 'div'
+ });
+
+ components.push(layerRow);
+ });
+
+ const addLayerButton = Button({
+ cls: 'icon-smaller light box-shadow',
+ style: 'border-radius: 3px',
+ icon: '#ic_add_24px',
+ text: 'Nytt ritlager',
+ async click() {
+ let title = drawOptions.layerTitle;
+ if (viewer.getLayersByProperty('title', title).length > 0) {
+ let i = 1;
+ while (i <= drawLayers.length) {
+ if (viewer.getLayersByProperty('title', `${title} ${i}`).length === 0) {
+ title = `${title} ${i}`;
+ break;
+ }
+ i += 1;
+ }
+ }
+ const addedLayer = await drawHandler.addLayer({ layerTitle: title });
+ drawHandler.setActiveLayer(addedLayer);
+ modal.closeModal();
+ layerForm.show();
+ }
+ });
+
+ const fileInput = InputFile({
+ labelCls: 'hidden',
+ inputCls: 'hidden',
+ change(e) {
+ const file = e.target.files[0];
+ const fileName = file.name.substring(0, file.name.lastIndexOf('.')) || file.name;
+ const reader = new FileReader();
+ reader.addEventListener(
+ 'loadend',
+ async () => {
+ if (validate.json(reader.result)) {
+ const features = reader.result;
+ const addedLayer = await drawHandler.addLayer({ features, layerTitle: fileName });
+ drawHandler.setActiveLayer(addedLayer);
+ modal.closeModal();
+ thisForm.show();
+ }
+ },
+ false
+ );
+ if (file) {
+ reader.readAsText(file);
+ }
+ }
+ });
+
+ const openBtn = Button({
+ cls: 'icon-smaller light box-shadow',
+ style: 'border-radius: 3px',
+ icon: '#ic_add_24px',
+ click() {
+ const inputEl = document.getElementById(fileInput.getId());
+ inputEl.value = null;
+ inputEl.click();
+ },
+ text: 'Importera ritlager',
+ ariaLabel: 'Importera ritlager'
+ });
+
+ const okButton = Button({
+ cls: 'icon-smaller light box-shadow',
+ style: 'border-radius: 3px',
+ icon: '#ic_check_24px',
+ text: 'OK',
+ click() {
+ modal.closeModal();
+ }
+ });
+
+ const buttonRow = El({
+ cls: 'flex row align-start justify-space-evenly margin-top-large',
+ components: [okButton, addLayerButton, fileInput, openBtn],
+ tagName: 'div'
+ });
+
+ components.push(buttonRow);
+
+ const modalContent = El({
+ cls: 'padding-small align-start justify-space-evenly',
+ components,
+ tagName: 'div'
+ });
+
+ modal = Modal({
+ title: 'Ritlager',
+ contentCmp: modalContent,
+ target: viewer.getId(),
+ style: 'max-width:100%;width:400px;'
+ });
+
+ modal.show();
+ }
+ });
+
+ const toolbarButtons = [];
+
+ const pointButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative',
+ click() {
+ if (this.getState() !== 'disabled') {
+ thisComponent.dispatch('toggleDraw', { tool: 'Point' });
+ }
+ },
+ icon: '#ic_place_24px',
+ tooltipText: 'Punkt',
+ tooltipPlacement: 'south',
+ tooltipStyle: 'bottom:-5px;',
+ state: 'inactive'
+ });
+
+ toolbarButtons.push(pointButton);
+
+ const lineButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative',
+ click() {
+ if (this.getState() !== 'disabled') {
+ thisComponent.dispatch('toggleDraw', { tool: 'LineString' });
+ }
+ },
+ icon: '#ic_timeline_24px',
+ tooltipText: 'Linje',
+ tooltipPlacement: 'south',
+ tooltipStyle: 'bottom:-5px;'
+ });
+
+ toolbarButtons.push(lineButton);
+
+ const polygonButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative',
+ click() {
+ if (this.getState() !== 'disabled') {
+ thisComponent.dispatch('toggleDraw', { tool: 'Polygon' });
+ }
+ },
+ icon: '#o_polygon_24px',
+ tooltipText: 'Polygon',
+ tooltipPlacement: 'south',
+ tooltipStyle: 'bottom:-5px;'
+ });
+
+ toolbarButtons.push(polygonButton);
+
+ const textButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative',
+ click() {
+ if (this.getState() !== 'disabled') {
+ thisComponent.dispatch('toggleDraw', { tool: 'Text' });
+ }
+ },
+ icon: '#ic_title_24px',
+ tooltipText: 'Text',
+ tooltipPlacement: 'south',
+ tooltipStyle: 'bottom:-5px;'
+ });
+
+ toolbarButtons.push(textButton);
+
+ if (showAttributeButton) {
+ layerAttributeButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative',
+ click() {
+ attributeForm.show();
+ },
+ icon: '#ic_textsms_24px',
+ tooltipText: 'Attribut',
+ tooltipPlacement: 'south',
+ tooltipStyle: 'bottom:-5px;',
+ state: 'disabled'
+ });
+
+ toolbarButtons.push(layerAttributeButton);
+ }
+
+ const stylewindowButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative',
+ click() {
+ const stylewindowEl = document.getElementById(stylewindow.getId());
+ stylewindowEl.classList.toggle('hidden');
+ if (this.getState() === 'active') {
+ this.setState('initial');
+ } else { this.setState('active'); }
+ },
+ icon: '#ic_palette_24px',
+ tooltipText: 'Stil',
+ tooltipPlacement: 'south',
+ tooltipStyle: 'bottom:-5px;'
+ });
+
+ toolbarButtons.push(stylewindowButton);
+
+ if (multipleLayers) {
+ const layerButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative',
+ click() {
+ layerForm.show();
+ },
+ icon: '#ic_layers_24px',
+ tooltipText: 'Lager',
+ tooltipPlacement: 'south',
+ tooltipStyle: 'bottom:-5px;'
+ });
+
+ toolbarButtons.push(layerButton);
+ }
+
+ if (showSaveButton) {
+ saveButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative',
+ click() {
+ thisComponent.dispatch('saveFeatures', true);
+ },
+ icon: '#ic_save_24px',
+ tooltipText: 'Spara',
+ tooltipPlacement: 'south',
+ tooltipStyle: 'bottom:-5px;',
+ state: 'disabled'
+ });
+
+ toolbarButtons.push(saveButton);
+ }
+
+ if (showDownloadButton) {
+ const downloadButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative',
+ click() {
+ const drawLayer = drawHandler.getActiveLayer();
+ const features = drawLayer.getSource().getFeatures();
+ exportToFile(features, 'geojson', {
+ featureProjection: viewer.getProjection().getCode(),
+ filename: drawLayer.get('title') || 'export'
+ });
+ },
+ icon: '#ic_download_24px',
+ tooltipText: 'Ladda ner',
+ tooltipPlacement: 'south',
+ tooltipStyle: 'bottom:-5px;'
+ });
+ toolbarButtons.push(downloadButton);
+ }
+
+ const deleteFeatureButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative',
+ click() {
+ if (drawHandler.getSelection().getLength() > 0 && window.confirm('Vill du radera det här objektet?')) {
+ thisComponent.dispatch('toggleDraw', { tool: 'delete' });
+ }
+ },
+ icon: '#ic_delete_24px',
+ tooltipText: 'Ta bort',
+ tooltipPlacement: 'south',
+ tooltipStyle: 'bottom:-5px;',
+ state: 'disabled'
+ });
+
+ toolbarButtons.push(deleteFeatureButton);
+
+ const closeToolbarButton = Button({
+ cls: 'padding-small icon-smaller round light box-shadow relative',
+ click() {
+ const stylewindowEl = document.getElementById(stylewindow.getId());
+ stylewindowEl.classList.add('hidden');
+ stylewindowButton.setState('initial');
+ viewer.dispatch('toggleClickInteraction', { name: 'draw', active: false });
+ },
+ icon: '#ic_close_24px',
+ tooltipText: 'Stäng',
+ tooltipPlacement: 'south',
+ tooltipStyle: 'bottom:-5px;'
+ });
+
+ toolbarButtons.push(closeToolbarButton);
+
+ const drawToolbarElement = El({
+ cls: 'flex fixed bottom-center divider-horizontal bg-inverted z-index-ontop-high no-print small-gap',
+ style: 'height: 2rem;',
+ components: toolbarButtons
+ });
+
+ return Component({
+ name: 'draw',
+ attributeForm,
+ getDrawHandler() {
+ return drawHandler;
+ },
+ saveButton,
+ getSelection() {
+ return drawHandler.getSelection();
+ },
+ getDrawOptions() {
+ return drawOptions;
+ },
+ getState() {
+ return drawHandler.getState();
+ },
+ isActive() {
+ return isActive;
+ },
+ onInit() {
+ thisComponent = this;
+ this.on('render', this.onRender);
+ },
+ onRender() {
+ drawTools = {
+ Point: pointButton,
+ LineString: lineButton,
+ Polygon: polygonButton,
+ Text: textButton
+ };
+ const extraTools = drawOptions.drawTools || [];
+ drawExtraTools(extraTools, viewer, drawTools);
+ },
+ onAdd(evt) {
+ viewer = evt.target;
+ map = viewer.getMap();
+ stylewindow = viewer.getStylewindow();
+ stylewindow.on('showStylewindow', function showStylewindow(e) {
+ if (e) {
+ stylewindowButton.setState('active');
+ const stylewindowEl = document.getElementById(this.getId());
+ stylewindowEl.classList.remove('hidden');
+ } else {
+ stylewindowButton.setState('initial');
+ const stylewindowEl = document.getElementById(this.getId());
+ stylewindowEl.classList.add('hidden');
+ }
+ });
+
+ if (placement.indexOf('screen') > -1) {
+ mapTools = `${viewer.getMain().getMapTools().getId()}`;
+ screenButtonContainer = El({
+ tagName: 'div',
+ cls: 'flex column'
+ });
+ screenButton = Button({
+ cls: 'o-print padding-small margin-bottom-smaller icon-smaller round light box-shadow',
+ click() {
+ if (!isActive) {
+ viewer.dispatch('toggleClickInteraction', { name: 'draw', active: true });
+ } else {
+ viewer.dispatch('toggleClickInteraction', { name: 'draw', active: false });
+ }
+ },
+ icon,
+ tooltipText: buttonText,
+ tooltipPlacement: 'east'
+ });
+ this.addComponent(screenButton);
+ }
+ if (placement.indexOf('menu') > -1) {
+ mapMenu = viewer.getControlByName('mapmenu');
+ menuItem = mapMenu.MenuItem({
+ click() {
+ if (!isActive) {
+ viewer.dispatch('toggleClickInteraction', { name: 'draw', active: true });
+ } else {
+ viewer.dispatch('toggleClickInteraction', { name: 'draw', active: false });
+ }
+ mapMenu.close();
+ },
+ icon,
+ title: buttonText
+ });
+ this.addComponent(menuItem);
+ }
+
+ this.addComponent(drawToolbarElement);
+ if (showAttributeButton) { this.addComponent(attributeForm); }
+ if (multipleLayers) { this.addComponent(layerForm); }
+ drawHandler = DrawHandler({
+ viewer,
+ annotation,
+ drawCmp: this,
+ stylewindow
+ });
+ drawHandler.restoreState(viewer.getUrlParams());
+ this.render();
+ viewer.on('toggleClickInteraction', (detail) => {
+ onEnableInteraction({ detail });
+ });
+ map.getLayers().on('remove', onLayerDelete.bind(this));
+ drawHandler.on('changeDraw', changeDrawState);
+ drawHandler.on('selectionChange', (detail) => {
+ if (deleteFeatureButton) {
+ const state = detail.features.getLength() > 0 ? 'initial' : 'disabled';
+ deleteFeatureButton.setState(state);
+ }
+ if (showAttributeButton) {
+ const state = detail.features.getLength() > 0 ? 'initial' : 'disabled';
+ layerAttributeButton.setState(state);
+ }
+ });
+ drawHandler.on('changeButtonState', (detail) => {
+ const state = detail.state;
+ textButton.setState(state);
+ polygonButton.setState(state);
+ pointButton.setState(state);
+ lineButton.setState(state);
+ });
+ this.on('toggleDraw', drawHandler.toggleDraw);
+ if (isActive) {
+ viewer.dispatch('toggleClickInteraction', { name: 'draw', active: true });
+ }
+ },
+ render() {
+ if (placement.indexOf('screen') > -1) {
+ let htmlString = `${screenButtonContainer.render()}`;
+ let el = dom.html(htmlString);
+ document.getElementById(mapTools).appendChild(el);
+ htmlString = screenButton.render();
+ el = dom.html(htmlString);
+ document.getElementById(screenButtonContainer.getId()).appendChild(el);
+ }
+ if (placement.indexOf('menu') > -1) {
+ mapMenu.appendMenuItem(menuItem);
+ }
+ const targetElement = document.getElementById(viewer.getMain().getId());
+ const htmlString = `
+
+
+
+ ${drawToolbarElement.render()}
+
+
+ `;
+
+ targetElement.appendChild(dom.html(htmlString));
+ this.dispatch('render');
+ }
+ });
+};
+
+export default Draw;
diff --git a/src/controls/draw/drawhandler.js b/src/controls/draw/drawhandler.js
new file mode 100644
index 000000000..4a1796a98
--- /dev/null
+++ b/src/controls/draw/drawhandler.js
@@ -0,0 +1,556 @@
+import Draw from 'ol/interaction/Draw';
+import Select from 'ol/interaction/Select';
+import Modify from 'ol/interaction/Modify';
+import DoubleClickZoom from 'ol/interaction/DoubleClickZoom';
+import GeoJSONFormat from 'ol/format/GeoJSON';
+import Feature from 'ol/Feature';
+import shapes from './shapes';
+import generateUUID from '../../utils/generateuuid';
+import { Component } from '../../ui';
+
+const DrawHandler = function DrawHandler(options = {}) {
+ const {
+ stylewindow,
+ drawCmp,
+ viewer
+ } = options;
+ let map;
+ let drawSource;
+ let drawLayer;
+ let draw;
+ let activeTool;
+ let select;
+ let modify;
+ let annotationField;
+ let drawOptions;
+ let thisComponent;
+
+ function disableDoubleClickZoom(evt) {
+ const featureType = evt.feature.getGeometry().getType();
+ const interactionsToBeRemoved = [];
+
+ if (featureType === 'Point') {
+ return;
+ }
+
+ map.getInteractions().forEach((interaction) => {
+ if (interaction instanceof DoubleClickZoom) {
+ interactionsToBeRemoved.push(interaction);
+ }
+ });
+ if (interactionsToBeRemoved.length > 0) {
+ map.removeInteraction(interactionsToBeRemoved[0]);
+ }
+ }
+
+ function onDrawStart(evt) {
+ if (evt.feature.getGeometry().getType() !== 'Point') {
+ disableDoubleClickZoom(evt);
+ }
+ }
+
+ function setActive(drawType) {
+ switch (drawType) {
+ case 'draw':
+ modify.setActive(true);
+ if (select) {
+ select.getFeatures().clear();
+ select.setActive(false);
+ }
+ break;
+ default:
+ activeTool = undefined;
+ map.removeInteraction(draw);
+ modify.setActive(true);
+ if (select) {
+ select.getFeatures().clear();
+ select.setActive(true);
+ }
+ break;
+ }
+ }
+
+ function onTextEnd(feature, textVal) {
+ // Remove the feature if no text is set
+ if (textVal === '') {
+ drawLayer.getSource().removeFeature(feature);
+ } else {
+ feature.set(annotationField, textVal);
+ }
+ setActive();
+ activeTool = undefined;
+ const details = {
+ feature,
+ layerName: drawLayer.get('name'),
+ action: 'insert',
+ tool: 'Text',
+ active: false
+ };
+ thisComponent.dispatch('changeDraw', details);
+ }
+
+ function addDoubleClickZoomInteraction() {
+ const allDoubleClickZoomInteractions = [];
+ map.getInteractions().forEach((interaction) => {
+ if (interaction instanceof DoubleClickZoom) {
+ allDoubleClickZoomInteractions.push(interaction);
+ }
+ });
+ if (allDoubleClickZoomInteractions.length < 1) {
+ map.addInteraction(new DoubleClickZoom());
+ }
+ }
+
+ function enableDoubleClickZoom() {
+ setTimeout(() => {
+ addDoubleClickZoomInteraction();
+ }, 100);
+ }
+
+ function onDrawEnd(evt) {
+ const feature = evt.feature;
+ if (activeTool === 'Text') {
+ onTextEnd(feature, 'Text');
+ stylewindow.dispatch('showStylewindow', true);
+ } else {
+ setActive();
+ activeTool = undefined;
+ }
+ enableDoubleClickZoom(evt);
+ if (drawLayer) {
+ feature.setId(generateUUID());
+ const styleObject = stylewindow.getStyleObject(feature);
+ feature.set('origostyle', styleObject);
+ }
+ const details = {
+ feature,
+ layerName: drawLayer.get('name'),
+ action: 'insert',
+ status: 'pending',
+ tool: feature.getGeometry().getType(),
+ active: false
+ };
+ thisComponent.dispatch('changeDraw', details);
+ if (select) {
+ select.getFeatures().clear();
+ select.getFeatures().push(feature);
+ }
+ }
+
+ function setDraw(tool, drawType) {
+ let geometryType = tool;
+ drawSource = drawLayer.getSource();
+ activeTool = tool;
+
+ if (activeTool === 'Text') {
+ geometryType = 'Point';
+ }
+
+ const opt = {
+ source: drawSource,
+ type: geometryType
+ };
+
+ if (drawType) {
+ Object.assign(opt, shapes(drawType));
+ }
+
+ map.removeInteraction(draw);
+ draw = new Draw(opt);
+ map.addInteraction(draw);
+ const details = {
+ tool,
+ active: true
+ };
+ thisComponent.dispatch('changeDraw', details);
+
+ draw.on('drawend', onDrawEnd, this);
+ draw.on('drawstart', onDrawStart, this);
+ }
+
+ function onDeleteSelected() {
+ const features = select.getFeatures();
+ let source;
+ if (features.getLength()) {
+ source = drawLayer.getSource();
+ features.forEach((feature) => {
+ const details = {
+ feature,
+ layerName: drawLayer.get('name'),
+ action: 'delete',
+ status: 'pending'
+ };
+ thisComponent.dispatch('changeDraw', details);
+ source.removeFeature(feature);
+ });
+ select.getFeatures().clear();
+ }
+ }
+
+ function onModifyEnd(evt) {
+ const feature = evt.features.item(0);
+ const details = {
+ feature,
+ layerName: drawLayer.get('name'),
+ action: 'update',
+ status: 'pending'
+ };
+ thisComponent.dispatch('changeDraw', details);
+ }
+
+ function cancelDraw(tool) {
+ setActive();
+ activeTool = undefined;
+ const details = {
+ tool,
+ active: false
+ };
+ thisComponent.dispatch('changeDraw', details);
+ }
+
+ function isActive() {
+ if (modify === undefined || select === undefined) {
+ return false;
+ }
+ return true;
+ }
+
+ function removeInteractions() {
+ if (isActive()) {
+ map.removeInteraction(modify);
+ map.removeInteraction(select);
+ map.removeInteraction(draw);
+ modify = undefined;
+ select = undefined;
+ draw = undefined;
+ }
+ }
+
+ function toggleDraw(detail) {
+ if (detail.clearTool) {
+ activeTool = undefined;
+ }
+ if (detail.tool === 'delete') {
+ onDeleteSelected();
+ } else if (detail.tool === 'cancel' && isActive()) {
+ cancelDraw(detail.tool);
+ removeInteractions();
+ } else if (detail.tool === activeTool) {
+ cancelDraw(detail.tool);
+ } else if (detail.tool === 'Polygon' || detail.tool === 'LineString' || detail.tool === 'Point' || detail.tool === 'Text') {
+ if (activeTool) {
+ cancelDraw(activeTool);
+ }
+ setActive('draw');
+ setDraw(detail.tool, detail.drawType);
+ }
+ }
+
+ function getFeaturesByIds(type, layer, ids) {
+ const source = layer.getSource();
+ const features = [];
+ if (type === 'delete') {
+ ids.forEach((id) => {
+ const dummy = new Feature();
+ dummy.setId(id);
+ features.push(dummy);
+ });
+ } else {
+ ids.forEach((id) => {
+ let feature;
+ if (source.getFeatureById(id)) {
+ feature = source.getFeatureById(id);
+ feature.unset('bbox');
+ features.push(feature);
+ }
+ });
+ }
+
+ return features;
+ }
+
+ const getSelection = () => select.getFeatures();
+
+ const getActiveLayer = () => drawLayer;
+
+ function getDrawLayers() {
+ const drawLayersArray = viewer.getLayersByProperty('drawlayer', true);
+ return drawLayersArray;
+ }
+
+ function onSelectChange() {
+ thisComponent.dispatch('selectionChange', { features: getSelection() });
+ }
+
+ function onSelectAdd(e) {
+ onSelectChange(e);
+ if (e.target) {
+ const feature = e.target.item(0);
+ const s = feature.get('origostyle') || {};
+ s.selected = true;
+ feature.set('origostyle', s);
+ feature.changed();
+ stylewindow.updateStylewindow(feature);
+ }
+ }
+
+ function onSelectRemove(e) {
+ onSelectChange(e);
+ const feature = e.element;
+ const s = feature.get('origostyle') || {};
+ s.selected = false;
+ feature.set('origostyle', s);
+ feature.changed();
+ stylewindow.restoreStylewindow();
+ }
+
+ function removeDrawInteractions() {
+ if (select) {
+ select.getFeatures().clear();
+ map.removeInteraction(select);
+ }
+ if (modify) {
+ map.removeInteraction(modify);
+ }
+ thisComponent.dispatch('changeButtonState', { state: 'disabled' });
+ }
+
+ function addDrawInteractions() {
+ removeDrawInteractions();
+ select = new Select({
+ layers: [drawLayer],
+ style: null,
+ hitTolerance: 5
+ });
+ modify = new Modify({
+ features: select.getFeatures()
+ });
+ map.addInteraction(select);
+ map.addInteraction(modify);
+ select.getFeatures().on('add', onSelectAdd, this);
+ select.getFeatures().on('remove', onSelectRemove, this);
+ select.getFeatures().on('change', onSelectChange, this);
+ modify.on('modifyend', onModifyEnd, this);
+ setActive();
+ if (drawLayer.getVisible()) {
+ thisComponent.dispatch('changeButtonState', { state: 'initial' });
+ }
+ }
+
+ function onChangeVisible() {
+ if (drawCmp.isActive()) {
+ if (drawLayer.getVisible()) {
+ addDrawInteractions();
+ } else {
+ removeDrawInteractions();
+ }
+ }
+ }
+
+ function setActiveLayer(layer) {
+ if (layer) {
+ if (drawLayer) {
+ drawLayer.un('change:visible', onChangeVisible);
+ }
+ drawLayer = layer;
+ drawLayer.on('change:visible', onChangeVisible);
+ onChangeVisible();
+ } else {
+ drawLayer = null;
+ removeDrawInteractions();
+ }
+ }
+
+ function onUpdate(feature, layerName) {
+ const details = {
+ feature,
+ layerName,
+ action: 'update',
+ status: 'pending'
+ };
+ thisComponent.dispatch('changeDraw', details);
+ }
+
+ function addLayer(layerParams = {}) {
+ const layerOptions = Object.assign({}, drawOptions, layerParams);
+ const {
+ layerTitle,
+ groupName,
+ groupTitle,
+ draggable,
+ layerId = generateUUID(),
+ layer,
+ features,
+ source,
+ visible,
+ styleByAttribute,
+ queryable,
+ removable,
+ exportable,
+ drawlayer
+ } = layerOptions;
+ let newLayer;
+ if (layer) { // Should maybe be handled differently, where does the layer come from?
+ newLayer = layer;
+ map.addLayer(newLayer);
+ } else {
+ if (!viewer.getGroup(groupName) && groupName !== 'none' && groupName !== 'root') {
+ viewer.addGroup({ title: groupTitle, name: groupName, expanded: true, draggable });
+ }
+ const newLayerOptions = {
+ group: groupName,
+ id: layerId,
+ name: layerId,
+ title: layerTitle,
+ zIndex: 7,
+ styleByAttribute,
+ visible,
+ queryable,
+ removable,
+ exportable,
+ drawlayer,
+ type: 'GEOJSON',
+ attributes: [
+ {
+ title: '',
+ name: 'popuptext',
+ type: 'text'
+ }
+ ]
+ };
+ if (source) {
+ newLayerOptions.source = source;
+ }
+ if (features) {
+ newLayerOptions.features = features;
+ }
+ newLayer = viewer.addLayer(newLayerOptions);
+ }
+ newLayer.getSource().forEachFeature((e) => {
+ e.on('change:origostyle', () => {
+ onUpdate(e, newLayer.get('name'));
+ });
+ e.on('change:popuptext', () => {
+ onUpdate(e, newLayer.get('name'));
+ });
+ });
+ newLayer.getSource().on('addfeature', (e) => {
+ e.feature.on('change:origostyle', () => {
+ onUpdate(e, newLayer.get('name'));
+ });
+ e.feature.on('change:popuptext', () => {
+ onUpdate(e, newLayer.get('name'));
+ });
+ });
+ return newLayer;
+ }
+
+ async function onEnableInteraction(e) {
+ if (e.detail.name === 'draw' && e.detail.active) {
+ if (drawLayer === undefined) {
+ const addedLayer = await thisComponent.addLayer();
+ setActiveLayer(addedLayer);
+ }
+ addDrawInteractions();
+ }
+ }
+
+ const getActiveTool = () => activeTool;
+
+ function getState() {
+ if (select) {
+ select.getFeatures().clear();
+ }
+ const drawLayers = getDrawLayers();
+ const layerArr = [];
+ drawLayers.forEach(layer => {
+ const source = layer.getSource();
+ const layerId = layer.get('name') || layer.get('id') || generateUUID();
+ const layerTitle = layer.get('title') || drawOptions.layerTitle || 'Ritlager';
+ const visible = layer.get('visible') || layer.getVisible();
+ const features = source.getFeatures();
+ const geojson = new GeoJSONFormat();
+ layerArr.push({ id: layerId, title: layerTitle, visible, features: geojson.writeFeatures(features) });
+ });
+ if (layerArr.length > 0) {
+ return {
+ layers: layerArr
+ };
+ }
+ return undefined;
+ }
+
+ function restoreState(urlParams) {
+ if (urlParams && urlParams.controls && urlParams.controls.draw) {
+ const state = urlParams.controls.draw;
+ // TODO: Sanity/data check
+ let activeLayer;
+ if (state.layers && state.layers.length > 0) {
+ state.layers.forEach(layer => {
+ const layerId = layer.id || generateUUID();
+ const layerTitle = layer.title || 'Ritlager';
+ const visible = layer.visible;
+ const features = layer.features;
+ const newLayer = thisComponent.addLayer({ layerId, layerTitle, visible });
+ const source = newLayer.getSource();
+ source.addFeatures(features);
+ activeLayer = newLayer;
+ });
+ } else if (state.features && state.features.length > 0) {
+ const features = state.features;
+ features.forEach(feature => {
+ const layerId = feature.get('layer');
+ if (layerId) {
+ const layer = viewer.getLayer(layerId);
+ if (layer) {
+ const source = layer.getSource();
+ source.addFeature(feature);
+ } else {
+ let layerTitle;
+ const newLayer = thisComponent.addLayer({ layerId, layerTitle });
+ const source = newLayer.getSource();
+ source.addFeature(feature);
+ activeLayer = newLayer;
+ }
+ } else {
+ if (drawLayer === undefined) {
+ drawLayer = thisComponent.addLayer();
+ }
+ const source = drawLayer.getSource();
+ source.addFeature(feature);
+ activeLayer = drawLayer;
+ }
+ });
+ }
+ if (activeLayer) {
+ setActiveLayer(activeLayer);
+ }
+ }
+ }
+ return Component({
+ name: 'drawHandler',
+ getDrawLayers,
+ getActiveLayer,
+ setActiveLayer,
+ addLayer,
+ getSelection,
+ getFeaturesByIds,
+ getState,
+ restoreState,
+ getActiveTool,
+ isActive,
+ toggleDraw,
+ onInit() {
+ thisComponent = this;
+ map = viewer.getMap();
+ annotationField = 'annotation';
+ drawOptions = drawCmp.getDrawOptions();
+ activeTool = undefined;
+ viewer.on('toggleClickInteraction', (detail) => {
+ onEnableInteraction({ detail });
+ });
+ }
+ });
+};
+
+export default DrawHandler;
diff --git a/src/controls/draw/drawtools.js b/src/controls/draw/drawtools.js
new file mode 100644
index 000000000..dfaeec900
--- /dev/null
+++ b/src/controls/draw/drawtools.js
@@ -0,0 +1,131 @@
+import dropDown from '../../dropdown';
+import utils from '../../utils';
+
+const createElement = utils.createElement;
+
+let viewer;
+
+const drawToolsSelector = function drawToolsSelector(extraTools, v, toolCmps) {
+ const toolNames = {
+ Polygon: 'Polygon',
+ Point: 'Punkt',
+ LineString: 'Linje',
+ box: 'Rektangel',
+ freehand: 'Frihandsläge'
+ };
+ viewer = v;
+ const drawCmp = viewer.getControlByName('draw');
+ let drawTools;
+ const map = viewer.getMap();
+ let active = false;
+ const activeCls = 'o-active';
+ const target = 'draw-toolbar-dropdown';
+ let activeTool;
+
+ function selectionModel() {
+ const selectOptions = drawTools.map((drawTool) => {
+ const obj = {};
+ obj.name = toolNames[drawTool];
+ obj.value = drawTool;
+ return obj;
+ });
+ return selectOptions;
+ }
+
+ function createDropDownOptions(dropDownTarget) {
+ return {
+ target: dropDownTarget,
+ selectOptions: selectionModel(drawTools),
+ activeTool: drawTools[0]
+ };
+ }
+
+ function close() {
+ if (active) {
+ // eslint-disable-next-line no-use-before-define
+ setActive(drawTools[0], false);
+ }
+ }
+
+ function changeDrawType(e) {
+ drawCmp.dispatch('toggleDraw', { tool: activeTool, drawType: e.detail.dataAttribute, clearTool: true });
+ close();
+ }
+
+ function addDropDown(options) {
+ dropDown(options.target, options.selectOptions, {
+ dataAttribute: 'shape',
+ active: options.activeTool
+ });
+ activeTool = options.activeTool;
+ document.getElementById(options.target).addEventListener('changeDropdown', changeDrawType);
+ }
+
+ function setActive(tool, state) {
+ if (state) {
+ if (drawTools.length > 1) {
+ active = true;
+
+ if (document.querySelector(`#${target}-${tool} > ul`)) {
+ document.querySelector(`#${target}-${tool} > ul`).remove();
+ }
+ addDropDown(createDropDownOptions(`${target}-${tool}`));
+ document.getElementById(`${target}-${tool}`).classList.add(activeCls);
+ map.once('click', close);
+ }
+ } else {
+ active = false;
+ if (tool && tool !== 'cancel' && document.querySelector(`[id^="${target}-${tool}"]`)) {
+ document.querySelector(`[id^="${target}-${tool}"]`).classList.remove(activeCls);
+ }
+ map.un('click', close);
+ }
+ }
+
+ function render() {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const tool in extraTools) {
+ if (Object.prototype.hasOwnProperty.call(extraTools, tool)) {
+ const popover = createElement('div', '', {
+ id: `${target}-${tool}`,
+ cls: 'o-popover'
+ });
+ const { body: popoverHTML } = new DOMParser().parseFromString(popover, 'text/html');
+ document.getElementById(toolCmps[tool].getId()).appendChild(popoverHTML.firstElementChild);
+ setActive(tool, false);
+ }
+ }
+ }
+
+ function setDrawTools(tool) {
+ if (extraTools[tool]) {
+ drawTools = extraTools[tool] ? extraTools[tool].slice(0) : [];
+ drawTools.unshift(tool);
+ } else {
+ drawTools = [tool];
+ }
+ }
+
+ function onChangeEdit(e) {
+ const { tool, active: state } = e;
+ if (state === true) {
+ setDrawTools(tool);
+ setActive(tool, true);
+ } else if (state === false) {
+ setActive(tool, false);
+ }
+ }
+
+ function addListener() {
+ drawCmp.getDrawHandler().on('changeDraw', onChangeEdit);
+ }
+
+ function init() {
+ render();
+ addListener();
+ }
+
+ init();
+};
+
+export default drawToolsSelector;
diff --git a/src/controls/draw/shapes.js b/src/controls/draw/shapes.js
new file mode 100644
index 000000000..06bbac099
--- /dev/null
+++ b/src/controls/draw/shapes.js
@@ -0,0 +1,18 @@
+import { createBox } from 'ol/interaction/Draw';
+
+export default (drawType) => {
+ const types = {
+ box: {
+ type: 'Circle',
+ geometryFunction: createBox()
+ },
+ freehand: {
+ freehand: true
+ }
+ };
+
+ if (Object.prototype.hasOwnProperty.call(types, drawType)) {
+ return types[drawType];
+ }
+ return {};
+};
diff --git a/src/controls/legend/overlay.js b/src/controls/legend/overlay.js
index 37c6e3903..2c4b0e23f 100644
--- a/src/controls/legend/overlay.js
+++ b/src/controls/legend/overlay.js
@@ -199,7 +199,7 @@ const OverlayLayer = function OverlayLayer(options) {
const features = layer.getSource().getFeatures();
exportToFile(features, format, {
featureProjection: viewer.getProjection().getCode(),
- filename: title || 'export'
+ filename: layer.get('title') || 'export'
});
e.preventDefault();
});
@@ -323,6 +323,11 @@ const OverlayLayer = function OverlayLayer(options) {
layerIcon.dispatch('change', { icon: newIcon });
};
+ const onLayerTitleChange = function onLayerTitleChange(newTitle) {
+ const labelEl = document.getElementById(label.getId());
+ labelEl.innerHTML = newTitle;
+ };
+
return Component({
name,
getLayer,
@@ -364,6 +369,9 @@ const OverlayLayer = function OverlayLayer(options) {
layer.on('change:style', () => {
onLayerStyleChange();
});
+ layer.on('change:title', (e) => {
+ onLayerTitleChange(e.target.get('title'));
+ });
},
render() {
let extendedLegendHtml = '';
diff --git a/src/controls/measure.js b/src/controls/measure.js
index 0f9054acd..4efeb4f32 100644
--- a/src/controls/measure.js
+++ b/src/controls/measure.js
@@ -1,22 +1,14 @@
-import { getArea, getLength } from 'ol/sphere';
-import VectorSource from 'ol/source/Vector';
-import VectorLayer from 'ol/layer/Vector';
-import DrawInteraction from 'ol/interaction/Draw';
-import Overlay from 'ol/Overlay';
import Feature from 'ol/Feature';
-import Polygon from 'ol/geom/Polygon';
-import Circle from 'ol/geom/Circle';
-import LineString from 'ol/geom/LineString';
-import Point from 'ol/geom/Point';
+import { Polygon, LineString, Point, Circle } from 'ol/geom';
+import { Draw, Modify, Snap } from 'ol/interaction';
+import { Vector as VectorLayer } from 'ol/layer';
import Projection from 'ol/proj/Projection';
-import * as Extent from 'ol/extent';
-import { Snap } from 'ol/interaction';
+import { Vector as VectorSource } from 'ol/source';
import { Collection } from 'ol';
import LayerGroup from 'ol/layer/Group';
import { unByKey } from 'ol/Observable';
import { Component, Icon, Element as El, Button, dom, Modal } from '../ui';
-import Style from '../style';
-import StyleTypes from '../style/styletypes';
+import * as drawStyles from '../style/drawstyles';
import replacer from '../utils/replacer';
const Measure = function Measure({
@@ -33,26 +25,14 @@ const Measure = function Measure({
snapLayers,
snapRadius = 15
} = {}) {
- const style = Style;
- const styleTypes = StyleTypes();
-
let map;
let activeButton;
let defaultButton;
let measure;
let type;
let sketch;
- let prevSketchLength = 0;
- let measureTooltip;
- let measureTooltipElement;
- let measureStyleOptions;
- let helpTooltip;
- let helpTooltipElement;
let markerIcon;
let markerElement;
- let vector;
- let source;
- let label;
let lengthTool;
let areaTool;
let elevationTool;
@@ -62,7 +42,6 @@ const Measure = function Measure({
let isActive = false;
let tempOverlayArray = [];
const overlayArray = [];
-
let viewer;
let measureElement;
let measureButton;
@@ -83,96 +62,83 @@ const Measure = function Measure({
let snapCollection;
let snapEventListenerKeys;
let snapActive = snapIsActive;
-
- function createStyle(feature) {
- const featureType = feature.getGeometry().getType();
- const measureStyle = featureType === 'LineString' ? style.createStyleRule(measureStyleOptions.linestring) : style.createStyleRule(measureStyleOptions.polygon);
-
- return measureStyle;
- }
- function setActive(state) {
- isActive = state;
- }
-
- function createHelpTooltip() {
- if (helpTooltipElement) {
- helpTooltipElement.parentNode.removeChild(helpTooltipElement);
- }
-
- helpTooltipElement = document.createElement('div');
- helpTooltipElement.className = 'o-tooltip o-tooltip-measure';
-
- helpTooltip = new Overlay({
- element: helpTooltipElement,
- offset: [15, 0],
- positioning: 'center-left'
- });
-
- tempOverlayArray.push(helpTooltip);
- map.addOverlay(helpTooltip);
- }
-
- function createMeasureTooltip() {
- if (measureTooltipElement) {
- measureTooltipElement.parentNode.removeChild(measureTooltipElement);
+ let tipPoint;
+ let projection;
+
+ const tipStyle = drawStyles.tipStyle;
+ const modifyStyle = drawStyles.modifyStyle;
+ const measureStyle = drawStyles.measureStyle;
+ const source = new VectorSource();
+ const modify = new Modify({ source, style: modifyStyle });
+
+ function styleFunction(feature, segments, drawType, tip) {
+ const styleScale = feature.get('styleScale') || 1;
+ const labelStyle = drawStyles.getLabelStyle(styleScale);
+ let styles = [measureStyle(styleScale)];
+ const geometry = feature.getGeometry();
+ const geomType = geometry.getType();
+ let point; let line; let label;
+ if (!drawType || drawType === geomType) {
+ if (geomType === 'Polygon') {
+ point = geometry.getInteriorPoint();
+ label = drawStyles.formatArea(geometry, useHectare, projection);
+ line = new LineString(geometry.getCoordinates()[0]);
+ } else if (geomType === 'LineString') {
+ point = new Point(geometry.getLastCoordinate());
+ label = drawStyles.formatLength(geometry, projection);
+ line = geometry;
+ }
+ }
+ if (segments && line) {
+ const segmentLabelStyle = drawStyles.getSegmentLabelStyle(line, projection);
+ styles = styles.concat(segmentLabelStyle);
+ }
+ if (label) {
+ labelStyle.setGeometry(point);
+ labelStyle.getText().setText(label);
+ styles.push(labelStyle);
+ }
+ if (
+ tip
+ && geomType === 'Point'
+ && !modify.getOverlay().getSource().getFeatures().length
+ ) {
+ tipPoint = geometry;
+ tipStyle.getText().setText(tip);
+ styles.push(tipStyle);
+ }
+ return styles;
+ }
+
+ const vector = new VectorLayer({
+ group: 'none',
+ name: 'measure',
+ title: 'Measure',
+ source,
+ zIndex: 8,
+ styleName: 'origoStylefunction',
+ style(feature) {
+ return styleFunction(feature, showSegmentLabels);
}
+ });
- measureTooltipElement = document.createElement('div');
- measureTooltipElement.className = 'o-tooltip o-tooltip-measure';
-
- measureTooltip = new Overlay({
- element: measureTooltipElement,
- offset: [0, -15],
- positioning: 'bottom-center',
- stopEvent: false
- });
-
- tempOverlayArray.push(measureTooltip);
- map.addOverlay(measureTooltip);
- }
-
- function formatLength(line) {
- const projection = map.getView().getProjection();
- const length = getLength(line, {
- projection
- });
- let output;
-
- if (length > 1000) {
- output = `${Math.round((length / 1000) * 100) / 100} km`;
- } else {
- output = `${Math.round(length * 100) / 100} m`;
+ function centerSketch() {
+ if (sketch) {
+ const geom = (sketch.getGeometry());
+ if (geom instanceof Polygon) {
+ const sketchCoord = geom.getCoordinates()[0];
+ sketchCoord.splice(-2, 1, map.getView().getCenter());
+ sketch.getGeometry().setCoordinates([sketchCoord]);
+ } else if (geom instanceof LineString) {
+ const sketchCoord = geom.getCoordinates();
+ sketchCoord.splice(-1, 1, map.getView().getCenter());
+ sketch.getGeometry().setCoordinates(sketchCoord);
+ }
}
-
- return output;
}
- function formatArea(polygon) {
- const projection = map.getView().getProjection();
- const area = getArea(polygon, {
- projection
- });
- let output;
-
- if (area > 10000000) {
- output = `${Math.round((area / 1000000) * 100) / 100} km2`;
- } else if (area > 10000 && useHectare) {
- output = `${Math.round((area / 10000) * 100) / 100} ha`;
- } else {
- output = `${Math.round(area * 100) / 100} m2`;
- }
-
- const htmlElem = document.createElement('span');
- htmlElem.innerHTML = output;
-
- [].forEach.call(htmlElem.children, (element) => {
- const el = element;
- if (el.tagName === 'SUP') {
- el.innerHTML = String.fromCharCode(el.innerHTML.charCodeAt(0) + 128);
- }
- });
-
- return htmlElem.textContent;
+ function setActive(state) {
+ isActive = state;
}
function getElevationAttribute(path, obj = {}) {
@@ -187,10 +153,6 @@ const Measure = function Measure({
start: '{',
end: '}'
};
- if (feature.getStyle() === null) {
- feature.setStyle(style.createStyleRule(measureStyleOptions.interaction));
- source.addFeature(feature);
- }
if (elevationTargetProjection && elevationTargetProjection !== viewer.getProjection().getCode()) {
const clone = feature.getGeometry().clone();
@@ -224,51 +186,28 @@ const Measure = function Measure({
easting,
northing
}, options);
-
- feature.setStyle(createStyle(feature));
- feature.getStyle()[0].getText().setText('Hämtar höjd...');
+ const styleScale = feature.get('styleScale') || 1;
+ const featureStyle = drawStyles.getLabelStyle(styleScale);
+ feature.setStyle(featureStyle);
+ feature.getStyle().getText().setText('Hämtar höjd...');
fetch(url).then(response => response.json({
cache: false
})).then((data) => {
const elevation = getElevationAttribute(elevationAttribute, data);
- feature.getStyle()[0].getText().setText(`${elevation.toFixed(1)} m`);
+ feature.getStyle().getText().setText(`${elevation.toFixed(1)} m`);
source.changed();
});
}
function addBuffer(feature, radius = 0) {
- if (feature.getStyle() === null) {
- feature.setStyle(style.createStyleRule(measureStyleOptions.interaction));
- source.addFeature(feature);
- }
- // Mark the central point of the circle
- feature.getStyle()[0].getText().setText('o');
- if (radius !== 0) {
+ if (radius > 0) {
bufferSize = radius;
}
- function addBufferToFeature() {
- const pointCenter = feature.getGeometry().getCoordinates();
- // Create a buffer around the point which was clicked on.
- const bufferCircle = new Circle(pointCenter, bufferSize);
- const bufferedFeature = new Feature(bufferCircle);
- // Create a new point at top of the circle to add a text with radius information
- const radiusText = new Point([pointCenter[0], bufferCircle.getExtent()[3]]);
- const radiusFeature = new Feature(radiusText);
- const featStyle = createStyle(feature);
- radiusFeature.setStyle(featStyle);
- // Remove stroke and fill only to leave the text styling from default measure style
- radiusFeature.getStyle()[0].setStroke(null);
- radiusFeature.getStyle()[0].setFill(null);
- // Offset the text so it dont't cover the circle
- radiusFeature.getStyle()[0].getText().setOffsetY(-10);
- radiusFeature.getStyle()[0].getText().setPlacement('line');
- radiusFeature.getStyle()[0].getText().setText(`${bufferSize} m`);
- vector.getSource().addFeature(bufferedFeature);
- vector.getSource().addFeature(radiusFeature);
- }
-
- addBufferToFeature();
+ const pointCenter = feature.getGeometry().getCoordinates();
+ const bufferCircle = new Circle(pointCenter, bufferSize);
+ feature.setGeometry(bufferCircle);
+ feature.setStyle((feat) => drawStyles.bufferStyleFunction(feat));
}
function clearSnapInteractions() {
@@ -278,199 +217,6 @@ const Measure = function Measure({
snapEventListenerKeys.clear();
}
- function placeMeasurementLabel(segment, coords) {
- const aa = segment.getExtent();
- const oo = Extent.getCenter(aa);
- measureElement = document.createElement('div');
- measureElement.className = 'o-tooltip o-tooltip-measure';
- measureElement.id = `measure_${coords.length}`;
- const labelOverlay = new Overlay({
- element: measureElement,
- positioning: 'center-center',
- stopEvent: true
- });
- tempOverlayArray.push(labelOverlay);
- labelOverlay.setPosition(oo);
- measureElement.innerHTML = formatLength(/** @type {LineString} */(segment));
- map.addOverlay(labelOverlay);
- if (coords.length < 6 && showSegmentLengths) {
- switch (type) {
- case 'LineString':
- if (coords.length === 3) {
- document.getElementById('measure_3').style.display = 'none';
- if (showSegmentLabels) {
- document.getElementById('measure_3').style.display = 'block';
- }
- }
- break;
- case 'Polygon':
- if (coords.length === 4) {
- document.getElementById('measure_4').style.display = 'none';
- if (showSegmentLabels) {
- document.getElementById('measure_4').style.display = 'block';
- }
- }
- break;
- case 'Point':
- if (showSegmentLabels) {
- document.getElementById('measure_2').style.display = 'block';
- } else {
- document.getElementById('measure_2').style.display = 'none';
- }
- break;
- default:
- break;
- }
- }
- if (!showSegmentLabels) {
- measureElement.style.display = 'none';
- }
- }
-
- // Takes a Polygon as input and adds area measurements on it
- function addArea(area) {
- const tempFeature = new Feature(area);
- const areaLabel = formatArea(area);
- tempFeature.setStyle(style.createStyleRule(measureStyleOptions.polygon));
- source.addFeature(tempFeature);
- const flatCoords = area.getCoordinates();
- for (let i = 0; i < flatCoords[0].length; i += 1) {
- if (i < flatCoords[0].length - 1) {
- const tempSegment = new LineString([flatCoords[0][i], flatCoords[0][i + 1]]);
- placeMeasurementLabel(tempSegment, flatCoords[0][i]);
- }
- }
- const totalLength = formatLength(new LineString(flatCoords[0]));
- tempFeature.getStyle()[0].getText().setText(`${areaLabel}\n${totalLength}`);
- }
-
- // Takes a LineString as input and adds length measurements on it
- function addLength(line) {
- const tempFeature = new Feature(line);
- const totalLength = formatLength(line);
- tempFeature.setStyle(style.createStyleRule(measureStyleOptions.linestring));
- source.addFeature(tempFeature);
- const flatCoords = line.getCoordinates();
- for (let i = 0; i < flatCoords.length; i += 1) {
- if (i < flatCoords.length - 1) {
- const tempSegment = new LineString([flatCoords[i], flatCoords[i + 1]]);
- placeMeasurementLabel(tempSegment, flatCoords[i]);
- }
- }
- tempFeature.getStyle()[0].getText().setText(totalLength);
- }
-
- function centerSketch() {
- if (sketch) {
- const geom = (sketch.getGeometry());
- if (geom instanceof Polygon) {
- const sketchCoord = geom.getCoordinates()[0];
- sketchCoord.splice(-2, 1, map.getView().getCenter());
- sketch.getGeometry().setCoordinates([sketchCoord]);
- } else if (geom instanceof LineString) {
- const sketchCoord = geom.getCoordinates();
- sketchCoord.splice(-1, 1, map.getView().getCenter());
- sketch.getGeometry().setCoordinates(sketchCoord);
- }
- }
- }
-
- // Display and move tooltips with pointer
- function pointerMoveHandler(evt) {
- const helpMsg = 'Klicka för att börja mäta';
- let tooltipCoord = evt.coordinate;
-
- if (sketch) {
- const geom = (sketch.getGeometry());
- let output = '';
- let coords;
- let area;
- let newNode;
- label = '';
-
- if (geom instanceof Polygon) {
- area = formatArea(/** @type {Polygon} */(geom));
- tooltipCoord = geom.getInteriorPoint().getCoordinates();
- coords = geom.getCoordinates()[0];
- newNode = coords.length > prevSketchLength && coords.length !== 3;
- prevSketchLength = coords.length;
- } else if (geom instanceof LineString) {
- tooltipCoord = geom.getLastCoordinate();
- coords = geom.getCoordinates();
- newNode = coords.length > prevSketchLength;
- prevSketchLength = coords.length;
- }
-
- let totalLength = 0;
- if (!(geom instanceof Point)) {
- totalLength = formatLength(/** @type {LineString} */(geom));
- }
- if (showSegmentLengths && !(geom instanceof Point)) {
- let lengthLastSegment = 0; // totalLength;
- let lastSegment;
- if (coords.length >= 1) {
- if (geom instanceof Polygon && coords.length > 2) {
- if (evt.type !== 'drawend') {
- // If this is a polygon in the progress of being drawn OL creates a extra vertices back to start that we need to ignore
- lastSegment = new LineString([coords[coords.length - 2], coords[coords.length - 3]]);
- const polygonAsLineString = /** @type {LineString} */ (geom);
- const lineStringWithoutLastSegment = new LineString(polygonAsLineString.getCoordinates()[0].slice(0, -1));
- totalLength = formatLength(lineStringWithoutLastSegment);
- } else {
- // Finish the polygon and put a label on the last verticies as well
- lastSegment = new LineString([coords[coords.length - 1], coords[coords.length - 2]]);
- placeMeasurementLabel(lastSegment, coords);
- }
- } else { // Draw segment while drawing is in progress
- lastSegment = new LineString([coords[coords.length - 1], coords[coords.length - 2]]);
- }
- // Create a label for the last drawn vertices and place it in the middle of it.
- lengthLastSegment = formatLength(/** @type {LineString} */(lastSegment));
- if ((newNode && evt.type !== 'drawend') && coords.length > 2) {
- let secondToLastSegment;
- if (geom instanceof Polygon && coords.length > 3) {
- secondToLastSegment = new LineString([coords[coords.length - 3], coords[coords.length - 4]]);
- } else {
- secondToLastSegment = new LineString([coords[coords.length - 2], coords[coords.length - 3]]);
- }
- if (secondToLastSegment) {
- placeMeasurementLabel(secondToLastSegment, coords);
- }
- }
- }
- if (area) {
- output = `${area}
`;
- label = `${area}\n`;
- }
- output += `${lengthLastSegment} (Totalt: ${totalLength})`;
- label += totalLength;
- } else if (area) {
- output = area;
- label = area;
- } else {
- output = totalLength;
- label += totalLength;
- }
-
- measureTooltipElement.innerHTML = output;
- measureTooltip.setPosition(tooltipCoord);
- }
-
- if (evt.type === 'pointermove') {
- helpTooltipElement.innerHTML = helpMsg;
- helpTooltip.setPosition(evt.coordinate);
- }
- }
-
- function resetSketch() {
- // unset sketch
- sketch = null;
- // unset tooltip so that a new one can be created
- measureTooltipElement = null;
- helpTooltipElement = null;
- viewer.removeOverlays(tempOverlayArray);
- }
-
function renderMarker() {
markerIcon = Icon({
icon: '#o_centerposition_24px',
@@ -493,6 +239,9 @@ const Measure = function Measure({
target: viewer.getId(),
style: 'width: auto;'
});
+ modal.on('closed', () => {
+ source.removeFeature(feature);
+ });
const bufferradiusEl = document.getElementById('bufferradius');
bufferradiusEl.focus();
const bufferradiusBtn = document.getElementById('bufferradiusBtn');
@@ -514,92 +263,6 @@ const Measure = function Measure({
});
}
- function disableInteraction() {
- if (activeButton) {
- document.getElementById(activeButton.getId()).classList.remove('active');
- }
- document.getElementById(measureButton.getId()).classList.remove('active');
- if (lengthTool) {
- document.getElementById(lengthToolButton.getId()).classList.add('hidden');
- }
- if (areaTool) {
- document.getElementById(areaToolButton.getId()).classList.add('hidden');
- }
- if (lengthTool || areaTool) {
- document.getElementById(undoButton.getId()).classList.add('hidden');
- }
- if (elevationTool) {
- document.getElementById(elevationToolButton.getId()).classList.add('hidden');
- }
- if (bufferTool) {
- document.getElementById(bufferToolButton.getId()).classList.add('hidden');
- }
- if (snap) {
- document.getElementById(toggleSnapButton.getId()).classList.add('hidden');
- }
- document.getElementById(measureButton.getId()).classList.add('tooltip');
- document.getElementById(clearButton.getId()).classList.add('hidden');
- if (showSegmentLengths) {
- document.getElementById(showSegmentLabelButton.getId()).classList.add('hidden');
- }
- if (touchMode && isActive) {
- document.getElementById(addNodeButton.getId()).classList.add('hidden');
- const markerIconElement = document.getElementById(`${markerIcon.getId()}`);
- markerIconElement.parentNode.removeChild(markerIconElement);
- }
- setActive(false);
- map.un('pointermove', pointerMoveHandler);
- map.removeInteraction(measure);
- if (snap) {
- clearSnapInteractions();
- }
- if (typeof helpTooltipElement !== 'undefined' && helpTooltipElement !== null) {
- if (helpTooltipElement.parentNode !== null) {
- helpTooltipElement.outerHTML = '';
- }
- }
- if (typeof measureTooltipElement !== 'undefined' && measureTooltipElement !== null) {
- if (measureTooltipElement.parentNode !== null) {
- measureTooltipElement.outerHTML = '';
- }
- }
- setActive(false);
- resetSketch();
- }
-
- function enableInteraction() {
- document.getElementById(measureButton.getId()).classList.add('active');
- if (lengthTool) {
- document.getElementById(lengthToolButton.getId()).classList.remove('hidden');
- }
- if (areaTool) {
- document.getElementById(areaToolButton.getId()).classList.remove('hidden');
- }
- if (elevationTool) {
- document.getElementById(elevationToolButton.getId()).classList.remove('hidden');
- }
- if (bufferTool) {
- document.getElementById(bufferToolButton.getId()).classList.remove('hidden');
- }
- if (snap) {
- document.getElementById(toggleSnapButton.getId()).classList.remove('hidden');
- }
- document.getElementById(measureButton.getId()).classList.remove('tooltip');
- document.getElementById(clearButton.getId()).classList.remove('hidden');
- document.getElementById(defaultButton.getId()).click();
- if (touchMode) {
- document.getElementById(addNodeButton.getId()).classList.remove('hidden');
- renderMarker();
- }
- if (showSegmentLengths) {
- document.getElementById(showSegmentLabelButton.getId()).classList.remove('hidden');
- if (showSegmentLabelButtonState) {
- document.getElementById(showSegmentLabelButton.getId()).classList.add('active');
- }
- }
- setActive(true);
- }
-
function createSnapInteractionForVectorLayer(layer) {
const state = layer.getLayerState();
// Using ol_uid because the Origo layer id is unreliable
@@ -672,80 +335,138 @@ const Measure = function Measure({
}
function addInteraction() {
- measure = new DrawInteraction({
+ const drawType = type || 'LineString';
+ const activeTip = '';
+ const idleTip = 'Klicka för att börja mäta';
+ let tip = idleTip;
+ measure = new Draw({
source,
- type,
- style: style.createStyleRule(measureStyleOptions.interaction),
- condition(evt) {
- return evt.originalEvent.pointerType !== 'touch';
+ type: drawType,
+ style(feature) {
+ return styleFunction(feature, showSegmentLabels, drawType, tip);
}
});
-
- map.addInteraction(measure);
- if (snap) {
- addSnapInteractions();
- }
- createMeasureTooltip();
- createHelpTooltip();
- if (!touchMode) {
- map.on('pointermove', pointerMoveHandler);
- }
-
- measure.on('drawstart', (evt) => {
- measure.getOverlay().getSource().getFeatures()[1].setStyle([]);
- sketch = evt.feature;
- sketch.on('change', pointerMoveHandler);
+ measure.on('drawstart', (e) => {
+ sketch = e.feature;
+ modify.setActive(false);
+ tip = activeTip;
if (touchMode) {
map.getView().on('change:center', centerSketch);
- } else {
- pointerMoveHandler(evt);
}
- document.getElementsByClassName('o-tooltip-measure')[1].remove();
-
- if (type === 'LineString' || type === 'Polygon') {
+ if (drawType === 'LineString' || drawType === 'Polygon') {
document.getElementById(undoButton.getId()).classList.remove('hidden');
}
- }, this);
-
+ });
measure.on('drawend', (evt) => {
const feature = evt.feature;
- sketch.un('change', pointerMoveHandler);
+ modifyStyle.setGeometry(tipPoint);
+ modify.setActive(true);
+ map.once('pointermove', () => {
+ modifyStyle.setGeometry();
+ });
+
if (touchMode) {
map.getView().un('change:center', centerSketch);
}
- pointerMoveHandler(evt);
- feature.setStyle(createStyle(feature));
- feature.getStyle()[0].getText().setText(label);
- document.getElementsByClassName('o-tooltip-measure')[0].remove();
- overlayArray.push(...tempOverlayArray);
- tempOverlayArray = [];
- resetSketch();
- createMeasureTooltip();
- createHelpTooltip();
-
+ tip = idleTip;
document.getElementById(undoButton.getId()).classList.add('hidden');
- if (feature.getGeometry().getType() === 'Point') {
- if (bufferTool) {
- if (document.getElementById(bufferToolButton.getId()).classList.contains('active')) {
- feature.getStyle()[0].getText().setText('');
- createRadiusModal(evt.feature);
- } else {
- feature.getStyle()[0].getText().setText(label);
- getElevation(evt.feature);
- }
- } else {
- feature.getStyle()[0].getText().setText(label);
- getElevation(evt.feature);
- }
+ if (activeButton.data.tool === 'buffer') {
+ feature.set('tool', 'buffer');
+ createRadiusModal(feature);
+ } else if (activeButton.data.tool === 'elevation') {
+ feature.set('tool', 'elevation');
+ getElevation(feature);
}
- }, this);
+ });
+
+ modify.on('modifyend', (evt) => {
+ evt.features.getArray().forEach(feat => {
+ if (feat.get('tool') === 'elevation') {
+ getElevation(feat);
+ }
+ });
+ });
+
+ modify.setActive(true);
+ map.addInteraction(measure);
+ map.addInteraction(modify);
+ if (snap) {
+ addSnapInteractions();
+ }
}
- function abort() {
- measure.abortDrawing();
- resetSketch();
- createMeasureTooltip();
- createHelpTooltip();
+ function disableInteraction() {
+ if (activeButton) {
+ document.getElementById(activeButton.getId()).classList.remove('active');
+ }
+ document.getElementById(measureButton.getId()).classList.remove('active');
+ if (lengthTool) {
+ document.getElementById(lengthToolButton.getId()).classList.add('hidden');
+ }
+ if (areaTool) {
+ document.getElementById(areaToolButton.getId()).classList.add('hidden');
+ }
+ if (lengthTool || areaTool) {
+ document.getElementById(undoButton.getId()).classList.add('hidden');
+ }
+ if (elevationTool) {
+ document.getElementById(elevationToolButton.getId()).classList.add('hidden');
+ }
+ if (bufferTool) {
+ document.getElementById(bufferToolButton.getId()).classList.add('hidden');
+ }
+ if (snap) {
+ document.getElementById(toggleSnapButton.getId()).classList.add('hidden');
+ }
+ document.getElementById(measureButton.getId()).classList.add('tooltip');
+ document.getElementById(clearButton.getId()).classList.add('hidden');
+ if (showSegmentLengths) {
+ document.getElementById(showSegmentLabelButton.getId()).classList.add('hidden');
+ }
+ if (touchMode && isActive) {
+ document.getElementById(addNodeButton.getId()).classList.add('hidden');
+ const markerIconElement = document.getElementById(`${markerIcon.getId()}`);
+ markerIconElement.parentNode.removeChild(markerIconElement);
+ }
+ setActive(false);
+ map.removeInteraction(measure);
+ map.removeInteraction(modify);
+ if (snap) {
+ clearSnapInteractions();
+ }
+ }
+
+ function enableInteraction() {
+ document.getElementById(measureButton.getId()).classList.add('active');
+ if (lengthTool) {
+ document.getElementById(lengthToolButton.getId()).classList.remove('hidden');
+ }
+ if (areaTool) {
+ document.getElementById(areaToolButton.getId()).classList.remove('hidden');
+ }
+ if (elevationTool) {
+ document.getElementById(elevationToolButton.getId()).classList.remove('hidden');
+ }
+ if (bufferTool) {
+ document.getElementById(bufferToolButton.getId()).classList.remove('hidden');
+ }
+ if (snap) {
+ document.getElementById(toggleSnapButton.getId()).classList.remove('hidden');
+ }
+ document.getElementById(measureButton.getId()).classList.remove('tooltip');
+ document.getElementById(clearButton.getId()).classList.remove('hidden');
+ document.getElementById(defaultButton.getId()).click();
+ if (touchMode) {
+ document.getElementById(addNodeButton.getId()).classList.remove('hidden');
+ renderMarker();
+ }
+ if (showSegmentLengths) {
+ document.getElementById(showSegmentLabelButton.getId()).classList.remove('hidden');
+ if (showSegmentLabelButtonState) {
+ document.getElementById(showSegmentLabelButton.getId()).classList.add('active');
+ }
+ }
+ setActive(true);
}
function toggleMeasure() {
@@ -764,7 +485,7 @@ const Measure = function Measure({
document.getElementById(undoButton.getId()).classList.add('hidden');
activeButton = button;
map.removeInteraction(measure);
- resetSketch();
+ map.removeInteraction(modify);
addInteraction();
}
@@ -804,17 +525,11 @@ const Measure = function Measure({
}
function undoLastPoint() {
- if ((type === 'LineString' && sketch.getGeometry().getCoordinates().length === 2) || (type === 'Polygon' && sketch.getGeometry().getCoordinates()[0].length <= 3)) {
- document.getElementsByClassName('o-tooltip-measure')[0].remove();
- document.getElementById(undoButton.getId()).classList.add('hidden');
- abort();
- } else {
- if (showSegmentLengths) document.getElementsByClassName('o-tooltip-measure')[1].remove();
- measure.removeLastPoint();
- if (touchMode) {
- centerSketch();
- }
+ measure.removeLastPoint();
+ if (touchMode) {
+ centerSketch();
}
+ // TODO: Remove undo button when feature has no geometry
}
function toggleSnap() {
@@ -845,11 +560,13 @@ const Measure = function Measure({
area.push(feature.getGeometry().getCoordinates());
break;
case 'Point':
- if (feature.getStyle()[0].getText().getText() === 'o') {
- buffer.push(feature.getGeometry().getCoordinates());
- } else if (feature.getStyle()[0].getText().getPlacement() === 'line') {
- bufferRadius.push(feature.getStyle()[0].getText().getText());
- } else {
+ case 'Circle':
+ if (feature.get('tool') === 'buffer') {
+ const radius = feature.getGeometry().getRadius();
+ const center = feature.getGeometry().getCenter();
+ bufferRadius.push(radius);
+ buffer.push(center);
+ } else if (feature.get('tool') === 'elevation') {
elevation.push(feature.getGeometry().getCoordinates());
}
break;
@@ -886,13 +603,16 @@ const Measure = function Measure({
function restoreState(params) {
if (params && params.controls && params.controls.measure) {
if (params.controls.measure.measureState.isActive) {
- enableInteraction();
+ isActive = false;
+ toggleMeasure();
}
// Restore areas
if (params.controls.measure.measureState && params.controls.measure.measureState.area && params.controls.measure.measureState.area.length > 0) {
if (Array.isArray(params.controls.measure.measureState.area)) {
params.controls.measure.measureState.area.forEach((item) => {
- addArea(new Polygon(item));
+ source.addFeature(new Feature({
+ geometry: new Polygon(item)
+ }));
});
}
}
@@ -900,7 +620,9 @@ const Measure = function Measure({
if (params.controls.measure.measureState && params.controls.measure.measureState.length && params.controls.measure.measureState.length.length > 0) {
if (Array.isArray(params.controls.measure.measureState.length)) {
params.controls.measure.measureState.length.forEach((item) => {
- addLength(new LineString(item));
+ source.addFeature(new Feature({
+ geometry: new LineString(item)
+ }));
});
}
}
@@ -908,9 +630,12 @@ const Measure = function Measure({
if (params.controls.measure.measureState && params.controls.measure.measureState.buffer && params.controls.measure.measureState.buffer.length > 0) {
if (Array.isArray(params.controls.measure.measureState.buffer)) {
for (let i = 0; i < params.controls.measure.measureState.buffer.length; i += 1) {
- let radius = params.controls.measure.measureState.bufferRadius[i];
- radius = radius.replace(' m', '');
- addBuffer(new Feature(new Point(params.controls.measure.measureState.buffer[i]), Number(radius)), Number(radius));
+ const radius = params.controls.measure.measureState.bufferRadius[i];
+ const point = params.controls.measure.measureState.buffer[i];
+ const feature = new Feature(new Point(point));
+ feature.set('tool', 'buffer');
+ source.addFeature(feature);
+ addBuffer(feature, radius);
}
}
}
@@ -918,7 +643,10 @@ const Measure = function Measure({
if (params.controls.measure.measureState && params.controls.measure.measureState.elevation && params.controls.measure.measureState.elevation.length > 0) {
if (Array.isArray(params.controls.measure.measureState.elevation)) {
for (let i = 0; i < params.controls.measure.measureState.elevation.length; i += 1) {
- getElevation(new Feature(new Point(params.controls.measure.measureState.elevation[i])));
+ const feature = new Feature(new Point(params.controls.measure.measureState.elevation[i]));
+ feature.set('tool', 'elevation');
+ source.addFeature(feature);
+ getElevation(feature);
}
}
}
@@ -947,6 +675,7 @@ const Measure = function Measure({
},
onAdd(evt) {
viewer = evt.target;
+ projection = viewer.getProjection().getCode();
touchMode = 'ontouchstart' in document.documentElement;
if (touchMode) {
addNodeButton = Button({
@@ -970,6 +699,8 @@ const Measure = function Measure({
cls: 'o-measure-segment-label padding-small margin-bottom-smaller icon-smaller round light box-shadow hidden',
click() {
toggleSegmentLabels();
+ vector.changed();
+ measure.getOverlay().changed();
},
icon: '#ic_linear_scale_24px',
tooltipText: 'Visa delsträckor',
@@ -978,24 +709,10 @@ const Measure = function Measure({
buttons.push(showSegmentLabelButton);
}
target = `${viewer.getMain().getMapTools().getId()}`;
-
map = viewer.getMap();
- source = new VectorSource();
- measureStyleOptions = styleTypes.getStyle('measure');
-
- // Drawn features
- vector = new VectorLayer({
- group: 'none',
- source,
- name: 'measure',
- visible: true,
- zIndex: 6
- });
-
map.addLayer(vector);
this.addComponents(buttons);
this.render();
- restoreState(viewer.getUrlParams());
viewer.on('toggleClickInteraction', (detail) => {
if (detail.name === 'measure' && detail.active) {
enableInteraction();
@@ -1003,6 +720,7 @@ const Measure = function Measure({
disableInteraction();
}
});
+ restoreState(viewer.getUrlParams());
},
onInit() {
lengthTool = measureTools.indexOf('length') >= 0;
@@ -1038,6 +756,7 @@ const Measure = function Measure({
type = 'LineString';
toggleType(this);
},
+ data: { tool: 'length' },
icon: '#ic_timeline_24px',
tooltipText: 'Längd',
tooltipPlacement: 'east'
@@ -1053,6 +772,7 @@ const Measure = function Measure({
type = 'Polygon';
toggleType(this);
},
+ data: { tool: 'area' },
icon: '#o_polygon_24px',
tooltipText: 'Yta',
tooltipPlacement: 'east'
@@ -1067,6 +787,7 @@ const Measure = function Measure({
type = 'Point';
toggleType(this);
},
+ data: { tool: 'elevation' },
icon: '#ic_height_24px',
tooltipText: 'Höjd',
tooltipPlacement: 'east'
@@ -1081,6 +802,7 @@ const Measure = function Measure({
type = 'Point';
toggleType(this);
},
+ data: { tool: 'buffer' },
icon: '#ic_adjust_24px',
tooltipText: 'Buffer',
tooltipPlacement: 'east'
@@ -1115,7 +837,7 @@ const Measure = function Measure({
clearButton = Button({
cls: 'o-measure-clear padding-small margin-bottom-smaller icon-smaller round light box-shadow hidden',
click() {
- abort();
+ measure.abortDrawing();
vector.getSource().clear();
viewer.removeOverlays(overlayArray);
},
diff --git a/src/controls/print/print-component.js b/src/controls/print/print-component.js
index 3136a8444..2a732e76d 100644
--- a/src/controls/print/print-component.js
+++ b/src/controls/print/print-component.js
@@ -669,6 +669,10 @@ const PrintComponent = function PrintComponent(options = {}) {
if (draganddropControl) draganddropControl.addInteraction();
},
render() {
+ viewer.dispatch('toggleClickInteraction', {
+ name: 'featureinfo',
+ active: true
+ });
if (deviceOnIos) {
// If user is on iOS we have to make sure the canvas ain't too heavy and make the browser crash
// eslint-disable-next-line no-underscore-dangle
diff --git a/src/controls/print/print-legend.js b/src/controls/print/print-legend.js
index 8508f743b..9e89f098b 100644
--- a/src/controls/print/print-legend.js
+++ b/src/controls/print/print-legend.js
@@ -266,7 +266,9 @@ const LayerRows = function LayerRows(options) {
const overlayEls = [];
overlays.forEach((layer) => {
- overlayEls.push(LayerRow({ layer, viewer }));
+ if (!layer.get('drawlayer')) {
+ overlayEls.push(LayerRow({ layer, viewer }));
+ }
});
const layerListCmp = Component({
async render() {
diff --git a/src/controls/print/print-resize.js b/src/controls/print/print-resize.js
index 64846568c..488bc47d7 100644
--- a/src/controls/print/print-resize.js
+++ b/src/controls/print/print-resize.js
@@ -46,7 +46,7 @@ export default function PrintResize(options = {}) {
// Resize features when DPI changes
const resizeFeature = function resizeFeature(style, feature, styleScale) {
- if (!Array.isArray(style)) {
+ if (style && !Array.isArray(style)) {
const image = style.getImage();
if (image) {
if (!(feature.ol_uid in imageSavedScale)) {
@@ -76,7 +76,7 @@ export default function PrintResize(options = {}) {
// Reset features that was resized in DPI changes
const resetFeature = function resetFeature(style, layer, feature) {
- if (!Array.isArray(style)) {
+ if (style && !Array.isArray(style)) {
const image = style.getImage();
if (image) {
if (typeof layersSaveStyle[layer.get('name')].imageScale[feature.ol_uid].scale !== 'undefined') {
@@ -404,8 +404,10 @@ export default function PrintResize(options = {}) {
} else if (features) {
features.forEach(feature => {
const featureStyle = feature.getStyle();
- if (featureStyle) {
- const styleScale = multiplyByFactor(1.5);
+ const styleScale = multiplyByFactor(1.5);
+ if (styleName === 'origoStylefunction' || styleName === 'default') {
+ feature.set('styleScale', styleScale);
+ } else if (featureStyle) {
if (Array.from(featureStyle).length === 0) {
resizeFeature(featureStyle, feature, styleScale);
} else {
@@ -439,11 +441,11 @@ export default function PrintResize(options = {}) {
const source = layer.getSource();
if (isVector(layer)) {
const features = source.getFeatures();
-
- let style = viewer.getStyle(layer.get('styleName'));
+ const styleName = layer.get('styleName');
+ let style = viewer.getStyle();
const clusterStyleName = layer.get('clusterStyle') ? layer.get('clusterStyle') : undefined;
- if (typeof layer.get('styleName') !== 'undefined') {
+ if (typeof layer.get('styleName') !== 'undefined' && layer.get('styleName') !== 'origoStylefunction' && layer.get('styleName') !== 'default') {
style = Style.createStyle({ style: layer.get('styleName'), viewer, clusterStyleName });
}
if (style) {
@@ -451,7 +453,11 @@ export default function PrintResize(options = {}) {
} else if (features) {
features.forEach(feature => {
const featureStyle = feature.getStyle();
- if (featureStyle) {
+ console.log(featureStyle);
+ console.log(styleName);
+ if (styleName === 'origoStylefunction' || styleName === 'default') {
+ feature.set('styleScale', 1);
+ } else if (featureStyle) {
if (Array.from(featureStyle).length === 0) {
resetFeature(featureStyle, layer, feature);
} else {
diff --git a/src/dropdown.js b/src/dropdown.js
index bddb5d45a..3942ec173 100644
--- a/src/dropdown.js
+++ b/src/dropdown.js
@@ -55,6 +55,7 @@ export default function dropDown(target, items, options) {
targetEl.dispatchEvent(dropdownEvent);
toggleActive(activeEl);
+ e.stopPropagation(e);
});
}
diff --git a/src/layer.js b/src/layer.js
index 4894ffcdb..905108a66 100644
--- a/src/layer.js
+++ b/src/layer.js
@@ -46,6 +46,11 @@ const Layer = function Layer(optOptions, viewer) {
layerOptions.extent = layerOptions.extent || viewer.getExtent();
layerOptions.sourceName = layerOptions.source;
layerOptions.styleName = layerOptions.style;
+ if (typeof layerOptions.style === 'function') {
+ layerOptions.styleName = 'stylefunction';
+ } else {
+ layerOptions.styleName = layerOptions.style;
+ }
if (layerOptions.id === undefined) {
layerOptions.id = name.split('__').shift();
}
diff --git a/src/layer/geojson.js b/src/layer/geojson.js
index 4440d71e5..6724dbdb1 100644
--- a/src/layer/geojson.js
+++ b/src/layer/geojson.js
@@ -1,43 +1,84 @@
import VectorSource from 'ol/source/Vector';
import GeoJSON from 'ol/format/GeoJSON';
+import Feature from 'ol/Feature';
import vector from './vector';
import isurl from '../utils/isurl';
-import { getStylewindowStyle } from '../controls/editor/stylewindow';
+import validate from '../utils/validate';
function createSource(options) {
- const vectorSource = new VectorSource({
- attributions: options.attribution,
- loader() {
- fetch(options.url, { headers: options.headers }).then(response => response.json()).then((data) => {
- vectorSource.addFeatures(vectorSource.getFormat().readFeatures(data));
- const numFeatures = vectorSource.getFeatures().length;
- for (let i = 0; i < numFeatures; i += 1) {
- vectorSource.forEachFeature((feature) => {
- if (!feature.getGeometry().intersectsExtent(options.customExtent)) {
- vectorSource.removeFeature(feature);
- }
- if (!feature.getId()) {
- if (feature.get(options.idField)) {
- feature.setId(feature.get(options.idField));
- } else {
- feature.setId(1000000 + i);
- }
- }
- if (feature.get('style') && options.styleByAttribute) {
- const featureStyle = getStylewindowStyle(feature, feature.get('style'));
- feature.setStyle(featureStyle);
+ const formatOptions = {
+ featureProjection: options.projectionCode,
+ dataProjection: options.dataProjection
+ };
+ if (options.url) {
+ const vectorSource = new VectorSource({
+ attributions: options.attribution,
+ loader() {
+ fetch(options.url, { headers: options.headers }).then(response => response.json()).then((data) => {
+ if (data.features) {
+ vectorSource.addFeatures(vectorSource.getFormat().readFeatures(data, formatOptions));
+ const numFeatures = vectorSource.getFeatures().length;
+ for (let i = 0; i < numFeatures; i += 1) {
+ vectorSource.forEachFeature((feature) => {
+ if (!feature.getGeometry().intersectsExtent(options.customExtent)) {
+ vectorSource.removeFeature(feature);
+ }
+ if (!feature.getId()) {
+ if (feature.get(options.idField)) {
+ feature.setId(feature.get(options.idField));
+ } else {
+ feature.setId(1000000 + i);
+ }
+ }
+ i += 1;
+ });
}
- i += 1;
- });
+ }
+ }).catch(error => console.warn(error));
+ },
+ format: new GeoJSON()
+ });
+ return vectorSource;
+ } else if (options.features) {
+ let features = options.features;
+ let featureArray = [];
+
+ if (typeof features === 'string' && validate.json(features)) { // JSON-string
+ features = JSON.parse(features);
+ }
+
+ if (typeof features === 'object' && (features.type === 'FeatureCollection' || features.type === 'Feature')) { // GeoJSON-object
+ featureArray = new GeoJSON().readFeatures(features, formatOptions);
+ } else if (Array.isArray(features)) {
+ for (let j = features.length - 1; j >= 0; j -= 1) {
+ let item = features[j];
+ if (typeof item === 'string' && validate.json(item)) { // JSON-string
+ item = JSON.parse(item);
+ }
+ if (typeof item === 'object' && (item.type === 'FeatureCollection' || item.type === 'Feature')) { // GeoJSON-object
+ const readFeatures = new GeoJSON().readFeatures(item, formatOptions);
+ featureArray.push(...readFeatures);
+ } else if (item instanceof Feature) { // Real OpenLayers feature
+ featureArray.push(item);
}
- }).catch(error => console.warn(error));
- },
- format: new GeoJSON({
- dataProjection: options.dataProjection,
- featureProjection: options.projectionCode
- })
- });
- return vectorSource;
+ }
+ }
+
+ featureArray.forEach((element, index) => {
+ if (!element.getId()) {
+ if (element.get(options.idField)) {
+ element.setId(element.get(options.idField));
+ } else if (element.get('id')) {
+ element.setId(element.get('id'));
+ } else {
+ element.setId(1000000 + index);
+ }
+ }
+ });
+
+ return new VectorSource({ features: featureArray });
+ }
+ return new VectorSource({});
}
const geojson = function geojson(layerOptions, viewer) {
@@ -55,20 +96,21 @@ const geojson = function geojson(layerOptions, viewer) {
sourceOptions.styleByAttribute = geojsonOptions.styleByAttribute;
if (geojsonOptions.projection) {
sourceOptions.dataProjection = geojsonOptions.projection;
- } else if (sourceOptions.projection) {
- sourceOptions.dataProjection = sourceOptions.projection;
- } else {
- sourceOptions.dataProjection = viewer.getProjectionCode();
}
sourceOptions.sourceName = layerOptions.source;
if (isurl(geojsonOptions.source)) {
sourceOptions.url = geojsonOptions.source;
- } else {
+ } else if (geojsonOptions.source && viewer.getMapSource()[geojsonOptions.source]) {
+ geojsonOptions.sourceName = geojsonOptions.source;
+ sourceOptions.url = viewer.getMapSource()[geojsonOptions.source].url;
+ } else if (geojsonOptions.source && geojsonOptions.source !== 'none') {
geojsonOptions.sourceName = geojsonOptions.source;
sourceOptions.url = geojsonOptions.source;
+ } else if (geojsonOptions.features) {
+ sourceOptions.features = geojsonOptions.features;
}
- sourceOptions.headers = layerOptions.headers;
+ sourceOptions.headers = layerOptions.headers;
const geojsonSource = createSource(sourceOptions);
return vector(geojsonOptions, geojsonSource, viewer);
};
diff --git a/src/layer/vector.js b/src/layer/vector.js
index 600f8f0f9..eb44bcb3d 100644
--- a/src/layer/vector.js
+++ b/src/layer/vector.js
@@ -5,6 +5,8 @@ import ClusterSource from 'ol/source/Cluster';
import Style from '../style';
export default function vector(opt, src, viewer) {
+ const stylewindow = viewer.getStylewindow();
+ const stylefunction = stylewindow.getStyleFunction;
const options = opt;
const source = src;
const distance = 60;
@@ -14,11 +16,20 @@ export default function vector(opt, src, viewer) {
switch (options.layerType) {
case 'vector':
{
+ if (opt.styleByAttribute) {
+ const projection = source.projection || viewer.getProjectionCode();
+ options.style = (feat) => stylefunction(feat, {}, projection);
+ options.styleName = 'origoStylefunction';
+ } else if (typeof opt.style === 'function') {
+ options.style = opt.style;
+ } else {
+ options.style = Style.createStyle({
+ style: options.style,
+ viewer
+ });
+ }
+
options.source = source;
- options.style = Style.createStyle({
- style: options.style,
- viewer
- });
vectorLayer = new VectorLayer(options);
break;
}
diff --git a/src/permalink/permalinkparser.js b/src/permalink/permalinkparser.js
index 48b0c11ca..24699e884 100644
--- a/src/permalink/permalinkparser.js
+++ b/src/permalink/permalinkparser.js
@@ -90,8 +90,18 @@ const controls = function controls(controlsStr) {
};
const controlDraw = function controlDraw(drawState) {
- const features = new GeoJSON().readFeatures(drawState.features);
- return { features };
+ const drawLayers = drawState.layers || [];
+ const layerArr = [];
+ let features = [];
+ drawLayers.forEach((element) => {
+ const layer = element;
+ layer.features = new GeoJSON().readFeatures(element.features);
+ layerArr.push(layer);
+ });
+ if (drawState.features) {
+ features = new GeoJSON().readFeatures(drawState.features);
+ }
+ return { features, layers: layerArr };
};
const legend = function legend(stateStr) {
diff --git a/src/permalink/permalinkstore.js b/src/permalink/permalinkstore.js
index 680beda12..349680745 100644
--- a/src/permalink/permalinkstore.js
+++ b/src/permalink/permalinkstore.js
@@ -15,7 +15,7 @@ permalinkStore.getSaveLayers = function getSaveLayers(layers) {
if (layer.get('defaultStyle') && layer.get('defaultStyle') !== layer.get('styleName')) saveLayer.sn = layer.get('altStyleIndex');
if (saveLayer.s || saveLayer.v) {
saveLayer.name = layer.get('name');
- if (saveLayer.name !== 'measure') {
+ if (saveLayer.name !== 'measure' && !layer.get('drawlayer')) {
saveLayers.push(urlparser.stringify(saveLayer, {
topmost: 'name'
}));
diff --git a/src/style/drawstyles.js b/src/style/drawstyles.js
new file mode 100644
index 000000000..024b43071
--- /dev/null
+++ b/src/style/drawstyles.js
@@ -0,0 +1,473 @@
+import {
+ Circle as CircleStyle,
+ Fill,
+ RegularShape,
+ Stroke,
+ Style,
+ Text
+} from 'ol/style';
+import { getArea, getLength } from 'ol/sphere';
+import { LineString, MultiPoint, Point } from 'ol/geom';
+
+function createRegularShape(type, size, fill, stroke) {
+ let style;
+ switch (type) {
+ case 'square':
+ style = new Style({
+ image: new RegularShape({
+ fill,
+ stroke,
+ points: 4,
+ radius: size,
+ angle: Math.PI / 4
+ })
+ });
+ break;
+
+ case 'triangle':
+ style = new Style({
+ image: new RegularShape({
+ fill,
+ stroke,
+ points: 3,
+ radius: size,
+ rotation: 0,
+ angle: 0
+ })
+ });
+ break;
+
+ case 'star':
+ style = new Style({
+ image: new RegularShape({
+ fill,
+ stroke,
+ points: 5,
+ radius: size,
+ radius2: size / 2.5,
+ angle: 0
+ })
+ });
+ break;
+
+ case 'cross':
+ style = new Style({
+ image: new RegularShape({
+ fill,
+ stroke,
+ points: 4,
+ radius: size,
+ radius2: 0,
+ angle: 0
+ })
+ });
+ break;
+
+ case 'x':
+ style = new Style({
+ image: new RegularShape({
+ fill,
+ stroke,
+ points: 4,
+ radius: size,
+ radius2: 0,
+ angle: Math.PI / 4
+ })
+ });
+ break;
+
+ case 'circle':
+ style = new Style({
+ image: new CircleStyle({
+ fill,
+ stroke,
+ radius: size
+ })
+ });
+ break;
+
+ default:
+ style = new Style({
+ image: new CircleStyle({
+ fill,
+ stroke,
+ radius: size
+ })
+ });
+ }
+ return style;
+}
+
+function formatLength(line, projection) {
+ const length = getLength(line, { projection });
+ let output;
+ if (length > 1000) {
+ output = `${Math.round((length / 1000) * 100) / 100} km`;
+ } else {
+ output = `${Math.round(length * 100) / 100} m`;
+ }
+ return output;
+}
+
+function formatArea(polygon, useHectare, projection) {
+ const area = getArea(polygon, { projection });
+ let output;
+ if (area > 10000000) {
+ output = `${Math.round((area / 1000000) * 100) / 100} km\xB2`;
+ } else if (area > 10000 && useHectare) {
+ output = `${Math.round((area / 10000) * 100) / 100} ha`;
+ } else {
+ output = `${Math.round(area * 100) / 100} m\xB2`;
+ }
+ return output;
+}
+
+function formatRadius(feat) {
+ let output;
+ const length = feat.getGeometry().getRadius();
+ if (length > 10000) {
+ output = `${Math.round((length / 1000) * 100) / 100} km`;
+ } else if (length > 100) {
+ output = `${Math.round(length)} m`;
+ } else {
+ output = `${Math.round(length * 100) / 100} m`;
+ }
+ return output;
+}
+
+const selectionStyle = new Style({
+ image: new CircleStyle({
+ radius: 5,
+ stroke: new Stroke({
+ color: 'rgba(0, 0, 0, 0.7)'
+ }),
+ fill: new Fill({
+ color: 'rgba(0, 153, 255, 0.8)'
+ })
+ }),
+ geometry(feature) {
+ let coords;
+ let pointGeometry;
+ const type = feature.getGeometry().getType();
+ if (type === 'Polygon') {
+ coords = feature.getGeometry().getCoordinates()[0];
+ pointGeometry = new MultiPoint(coords);
+ } else if (type === 'LineString') {
+ coords = feature.getGeometry().getCoordinates();
+ pointGeometry = new MultiPoint(coords);
+ } else if (type === 'Point') {
+ coords = feature.getGeometry().getCoordinates();
+ pointGeometry = new Point(coords);
+ }
+ return pointGeometry;
+ }
+});
+
+const measureStyle = function measureStyle(scale = 1) {
+ return new Style({
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 0.4)'
+ }),
+ stroke: new Stroke({
+ color: 'rgba(0, 0, 0, 0.8)',
+ lineDash: [10 * scale, 10 * scale],
+ width: 2 * scale
+ }),
+ image: new CircleStyle({
+ radius: 5 * scale,
+ stroke: new Stroke({
+ color: 'rgba(0, 0, 0, 0.7)'
+ }),
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 0.2)'
+ })
+ })
+ });
+};
+
+const labelStyle = function labelStyle(scale = 1) {
+ return new Style({
+ text: new Text({
+ font: `${14 * scale}px Calibri,sans-serif`,
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 1)'
+ }),
+ backgroundFill: new Fill({
+ color: 'rgba(0, 0, 0, 0.7)'
+ }),
+ padding: [3 * scale, 3 * scale, 3 * scale, 3 * scale],
+ textBaseline: 'bottom',
+ offsetY: -15 * scale
+ }),
+ image: new RegularShape({
+ radius: 8 * scale,
+ points: 3,
+ angle: Math.PI,
+ displacement: [0, 10 * scale],
+ fill: new Fill({
+ color: 'rgba(0, 0, 0, 0.7)'
+ })
+ })
+ });
+};
+
+function getLabelStyle(scale = 1) {
+ return labelStyle(scale).clone();
+}
+
+const tipStyle = new Style({
+ text: new Text({
+ font: '12px Calibri,sans-serif',
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 1)'
+ }),
+ backgroundFill: new Fill({
+ color: 'rgba(0, 0, 0, 0.4)'
+ }),
+ padding: [2, 2, 2, 2],
+ textAlign: 'left',
+ offsetX: 15
+ })
+});
+
+const modifyStyle = new Style({
+ image: new CircleStyle({
+ radius: 5,
+ stroke: new Stroke({
+ color: 'rgba(0, 0, 0, 0.7)'
+ }),
+ fill: new Fill({
+ color: 'rgba(0, 153, 255, 0.8)'
+ })
+ }),
+ text: new Text({
+ text: 'Dra för att ändra',
+ font: '12px Calibri,sans-serif',
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 1)'
+ }),
+ backgroundFill: new Fill({
+ color: 'rgba(0, 0, 0, 0.7)'
+ }),
+ padding: [2, 2, 2, 2],
+ textAlign: 'left',
+ offsetX: 15
+ })
+});
+
+const segmentStyle = function segmentStyle(scale = 1) {
+ return new Style({
+ text: new Text({
+ font: `${12 * scale}px Calibri,sans-serif`,
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 1)'
+ }),
+ backgroundFill: new Fill({
+ color: 'rgba(0, 0, 0, 0.4)'
+ }),
+ padding: [2 * scale, 2 * scale, 2 * scale, 2 * scale],
+ textBaseline: 'bottom',
+ offsetY: -12 * scale
+ }),
+ image: new RegularShape({
+ radius: 6 * scale,
+ points: 3,
+ angle: Math.PI,
+ displacement: [0, 8 * scale],
+ fill: new Fill({
+ color: 'rgba(0, 0, 0, 0.4)'
+ })
+ })
+ });
+};
+
+function getBufferLabelStyle(label = '', scale = 1) {
+ return new Style({
+ text: new Text({
+ font: `${14 * scale}px Calibri,sans-serif`,
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 1)'
+ }),
+ backgroundFill: new Fill({
+ color: 'rgba(0, 0, 0, 0.7)'
+ }),
+ padding: [3 * scale, 3 * scale, 3 * scale, 3 * scale],
+ textBaseline: 'bottom',
+ offsetY: -15 * scale,
+ text: label
+ }),
+ image: new RegularShape({
+ radius: 8 * scale,
+ points: 3,
+ angle: Math.PI,
+ displacement: [0, 10 * scale],
+ fill: new Fill({
+ color: 'rgba(0, 0, 0, 0.7)'
+ })
+ }),
+ geometry: (feat) => {
+ const coordinates = [feat.getGeometry().getCenter()[0], feat.getGeometry().getExtent()[3]];
+ return new Point(coordinates);
+ }
+ });
+}
+
+function getSegmentLabelStyle(line, projection, scale = 1, segmentStyles = []) {
+ let count = 0;
+ const style = [];
+ line.forEachSegment((a, b) => {
+ const segment = new LineString([a, b]);
+ const segmentLabel = formatLength(segment, projection);
+ if (segmentStyles.length - 1 < count) {
+ segmentStyles.push(segmentStyle(scale).clone());
+ }
+ const segmentPoint = new Point(segment.getCoordinateAt(0.5));
+ segmentStyles[count].setGeometry(segmentPoint);
+ segmentStyles[count].getText().setText(segmentLabel);
+ style.push(segmentStyles[count]);
+ count += 1;
+ });
+ return style;
+}
+
+function getBufferPointStyle(scale = 1) {
+ return new Style({
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 0.2)'
+ }),
+ stroke: new Stroke({
+ color: 'rgba(0, 0, 0, 0.5)',
+ lineDash: [10 * scale, 10 * scale],
+ width: 2 * scale
+ }),
+ image: new CircleStyle({
+ radius: 5 * scale,
+ stroke: new Stroke({
+ color: 'rgba(0, 0, 0, 0.7)'
+ }),
+ fill: new Fill({
+ color: 'rgba(255, 255, 255, 0.2)'
+ })
+ }),
+ geometry: (feat) => {
+ const coordinates = feat.getGeometry().getCenter();
+ return new Point(coordinates);
+ }
+ });
+}
+
+function bufferStyleFunction(feature) {
+ const styleScale = feature.get('styleScale') || 1;
+ const bufferLabelStyle = getBufferLabelStyle(`${formatRadius(feature)}`, styleScale);
+ const pointStyle = getBufferPointStyle(styleScale);
+ return [measureStyle(styleScale), bufferLabelStyle, pointStyle];
+}
+
+const measure = {
+ linestring: [{
+ geometry: 'endPoint',
+ circle: {
+ fill: {
+ color: [0, 153, 255, 1]
+ },
+ stroke: {
+ color: [0, 153, 255, 1],
+ width: 1
+ },
+ radius: 3
+ },
+ text: {
+ font: 'bold 13px "Helvetica Neue", Helvetica, Arial, sans-serif',
+ textBaseline: 'bottom',
+ textAlign: 'center',
+ offsetY: -4,
+ fill: {
+ color: [0, 153, 255, 1]
+ },
+ stroke: {
+ color: [255, 255, 255, 0.8],
+ width: 4
+ }
+ }
+ },
+ {
+ stroke: {
+ color: [0, 153, 255, 1],
+ width: 2
+ }
+ }
+ ],
+ polygon: [{
+ fill: {
+ color: [255, 255, 255, 0.4]
+ },
+ stroke: {
+ color: [0, 153, 255, 1],
+ width: 2
+ },
+ text: {
+ font: 'bold 13px "Helvetica Neue", Helvetica, Arial, sans-serif',
+ textBaseline: 'middle',
+ textAlign: 'center',
+ overflow: 'true',
+ fill: {
+ color: [0, 153, 255, 1]
+ },
+ stroke: {
+ color: [255, 255, 255, 0.8],
+ width: 4
+ }
+ }
+ }],
+ interaction: [{
+ fill: {
+ color: [255, 255, 255, 0.2]
+ },
+ stroke: {
+ color: [0, 0, 0, 0.5],
+ lineDash: [10, 10],
+ width: 2
+ },
+ circle: {
+ radius: 5,
+ stroke: {
+ color: [0, 0, 0, 0.7]
+ },
+ fill: {
+ color: [255, 255, 255, 0.2]
+ }
+ },
+ text: {
+ font: 'bold 13px "Helvetica Neue", Helvetica, Arial, sans-serif',
+ textBaseline: 'middle',
+ textAlign: 'center',
+ overflow: 'true',
+ fill: {
+ color: [0, 153, 255, 1]
+ },
+ stroke: {
+ color: [255, 255, 255, 0.8],
+ width: 4
+ }
+ }
+ }]
+};
+
+export {
+ bufferStyleFunction,
+ createRegularShape,
+ formatLength,
+ formatArea,
+ formatRadius,
+ getBufferLabelStyle,
+ getBufferPointStyle,
+ getLabelStyle,
+ getSegmentLabelStyle,
+ labelStyle,
+ measure,
+ measureStyle,
+ modifyStyle,
+ segmentStyle,
+ selectionStyle,
+ tipStyle
+};
diff --git a/src/style/hextorgba.js b/src/style/hextorgba.js
new file mode 100644
index 000000000..cea0ac494
--- /dev/null
+++ b/src/style/hextorgba.js
@@ -0,0 +1,21 @@
+const isValidHex = (hex) => /^#([A-Fa-f0-9]{3,4}){1,2}$/.test(hex);
+
+const getChunksFromString = (st, chunkSize) => st.match(new RegExp(`.{${chunkSize}}`, 'g'));
+
+const convertHexUnitTo256 = (hexStr) => parseInt(hexStr.repeat(2 / hexStr.length), 16);
+
+const getAlphafloat = (a, alpha) => {
+ if (typeof a !== 'undefined') { return a / 255; }
+ if ((typeof alpha !== 'number') || alpha < 0 || alpha > 1) {
+ return 1;
+ }
+ return alpha;
+};
+
+export default function hexToRGBA(hex, alpha) {
+ if (!isValidHex(hex)) { throw new Error('Invalid HEX'); }
+ const chunkSize = Math.floor((hex.length - 1) / 3);
+ const hexArr = getChunksFromString(hex.slice(1), chunkSize);
+ const [r, g, b, a] = hexArr.map(convertHexUnitTo256);
+ return `rgba(${r}, ${g}, ${b}, ${getAlphafloat(a, alpha)})`;
+}
diff --git a/src/style/measure.js b/src/style/measure.js
deleted file mode 100644
index 43fa69cb4..000000000
--- a/src/style/measure.js
+++ /dev/null
@@ -1,91 +0,0 @@
-const measure = {
- linestring: [{
- geometry: 'endPoint',
- circle: {
- fill: {
- color: [0, 153, 255, 1]
- },
- stroke: {
- color: [0, 153, 255, 1],
- width: 1
- },
- radius: 3
- },
- text: {
- font: 'bold 13px "Helvetica Neue", Helvetica, Arial, sans-serif',
- textBaseline: 'bottom',
- textAlign: 'center',
- offsetY: -4,
- fill: {
- color: [0, 153, 255, 1]
- },
- stroke: {
- color: [255, 255, 255, 0.8],
- width: 4
- }
- }
- },
- {
- stroke: {
- color: [0, 153, 255, 1],
- width: 2
- }
- }
- ],
- polygon: [{
- fill: {
- color: [255, 255, 255, 0.4]
- },
- stroke: {
- color: [0, 153, 255, 1],
- width: 2
- },
- text: {
- font: 'bold 13px "Helvetica Neue", Helvetica, Arial, sans-serif',
- textBaseline: 'middle',
- textAlign: 'center',
- overflow: 'true',
- fill: {
- color: [0, 153, 255, 1]
- },
- stroke: {
- color: [255, 255, 255, 0.8],
- width: 4
- }
- }
- }],
- interaction: [{
- fill: {
- color: [255, 255, 255, 0.2]
- },
- stroke: {
- color: [0, 0, 0, 0.5],
- lineDash: [10, 10],
- width: 2
- },
- circle: {
- radius: 5,
- stroke: {
- color: [0, 0, 0, 0.7]
- },
- fill: {
- color: [255, 255, 255, 0.2]
- }
- },
- text: {
- font: 'bold 13px "Helvetica Neue", Helvetica, Arial, sans-serif',
- textBaseline: 'middle',
- textAlign: 'center',
- overflow: 'true',
- fill: {
- color: [0, 153, 255, 1]
- },
- stroke: {
- color: [255, 255, 255, 0.8],
- width: 4
- }
- }
- }]
-};
-
-export default measure;
diff --git a/src/style/stylefunctions/default.js b/src/style/stylefunctions/default.js
index 87d495984..cf166350b 100644
--- a/src/style/stylefunctions/default.js
+++ b/src/style/stylefunctions/default.js
@@ -32,44 +32,47 @@ export default function defaultStyle() {
const styles = [];
return function style(feature) {
+ const styleScale = feature.get('styleScale') || 1;
polygon.setZIndex(1);
line.setZIndex(10);
let length = 0;
+ const width = 1 * styleScale;
+ const radius = 5 * styleScale;
const geom = feature.getGeometry().getType();
switch (geom) {
case 'Polygon':
stroke.setColor(getColor('blue'));
- stroke.setWidth(1);
+ stroke.setWidth(width);
fill.setColor(getColor('blue', 0.8));
styles[length] = strokedPolygon;
length += 1;
break;
case 'MultiPolygon':
stroke.setColor(getColor('blue'));
- stroke.setWidth(1);
+ stroke.setWidth(width);
fill.setColor(getColor('blue', 0.8));
styles[length] = strokedPolygon;
length += 1;
break;
case 'LineString':
stroke.setColor(getColor('red'));
- stroke.setWidth(1);
+ stroke.setWidth(width);
styles[length] = line;
length += 1;
break;
case 'MultiLineString':
stroke.setColor(getColor('red'));
- stroke.setWidth(1);
+ stroke.setWidth(width);
styles[length] = line;
length += 1;
break;
case 'Point':
stroke.setColor(getColor('blue'));
- stroke.setWidth(1);
+ stroke.setWidth(width);
fill.setColor(getColor('blue', 0.8));
point = new Style({
image: new Circle({
- radius: 5,
+ radius,
fill,
stroke
}),
@@ -80,11 +83,11 @@ export default function defaultStyle() {
break;
case 'MultiPoint':
stroke.setColor(getColor('blue'));
- stroke.setWidth(1);
+ stroke.setWidth(width);
fill.setColor(getColor('blue', 0.8));
point = new Style({
image: new Circle({
- radius: 5,
+ radius,
fill,
stroke
}),
@@ -95,7 +98,7 @@ export default function defaultStyle() {
break;
default:
stroke.setColor(getColor('blue'));
- stroke.setWidth(1);
+ stroke.setWidth(width);
fill.setColor(getColor('blue', 0.8));
styles[length] = strokedPolygon;
length += 1;
diff --git a/src/style/styletemplate.js b/src/style/styletemplate.js
new file mode 100644
index 000000000..785dc19ed
--- /dev/null
+++ b/src/style/styletemplate.js
@@ -0,0 +1,104 @@
+export default function styleTemplate(palette, swStyle) {
+ const colorArray = palette;
+ let fillHtml = '`;
+
+ let strokeHtml = 'Kantlinje
+
+
+
+
`;
+
+ const pointHtml = `Punkt
+
+
+
`;
+
+ const textHtml = ``;
+
+ const measureHtml = ``;
+
+ return textHtml + pointHtml + fillHtml + strokeHtml + measureHtml;
+}
diff --git a/src/style/styletypes.js b/src/style/styletypes.js
index ea3164e84..9fcff5903 100644
--- a/src/style/styletypes.js
+++ b/src/style/styletypes.js
@@ -1,5 +1,5 @@
import pin from './pin';
-import measure from './measure';
+import { measure } from './drawstyles';
import multiselection from './multiselection';
export default function styletypes() {
diff --git a/src/style/stylewindow.js b/src/style/stylewindow.js
new file mode 100644
index 000000000..222c63701
--- /dev/null
+++ b/src/style/stylewindow.js
@@ -0,0 +1,509 @@
+import { LineString, Point } from 'ol/geom';
+import Select from 'ol/interaction/Select';
+import Fill from 'ol/style/Fill';
+import Stroke from 'ol/style/Stroke';
+import Style from 'ol/style/Style';
+import Text from 'ol/style/Text';
+
+import * as drawStyles from './drawstyles';
+import styleTemplate from './styletemplate';
+import hexToRgba from './hextorgba';
+import { Component, Button, Element, dom } from '../ui';
+
+const Stylewindow = function Stylewindow(optOptions = {}) {
+ const {
+ title = 'Anpassa stil',
+ cls = 'control overflow-hidden hidden',
+ css = '',
+ viewer,
+ closeIcon = '#ic_close_24px',
+ palette = ['rgb(166,206,227)', 'rgb(31,120,180)', 'rgb(178,223,138)', 'rgb(51,160,44)', 'rgb(251,154,153)', 'rgb(227,26,28)', 'rgb(253,191,111)']
+ } = optOptions;
+
+ let annotationField;
+ let swStyle = {};
+ let mapProjection;
+ const swDefaults = {
+ fillColor: 'rgb(0,153,255)',
+ fillOpacity: 0.75,
+ strokeColor: 'rgb(0,153,255)',
+ strokeOpacity: 1,
+ strokeWidth: 2,
+ strokeType: 'line',
+ pointSize: 10,
+ pointType: 'circle',
+ textSize: 20,
+ textString: 'Text',
+ textFont: '"Helvetica Neue", Helvetica, Arial, sans-serif',
+ showMeasureSegments: false,
+ showMeasure: false,
+ selected: false
+ };
+
+ function escapeQuotes(s) {
+ return s.replace(/'/g, "''");
+ }
+
+ function rgbToArray(colorString, opacity = 1) {
+ const colorArray = colorString.replace(/[^\d,.]/g, '').split(',');
+ colorArray[3] = opacity;
+ return colorArray;
+ }
+
+ function rgbToRgba(colorString, opacity = 1) {
+ const colorArray = colorString.replace(/[^\d,.]/g, '').split(',');
+ return `rgba(${colorArray[0]},${colorArray[1]},${colorArray[2]},${opacity})`;
+ }
+
+ function rgbaToRgb(colorString) {
+ const colorArray = colorString.replace(/[^\d,.]/g, '').split(',');
+ return `rgb(${colorArray[0]},${colorArray[1]},${colorArray[2]})`;
+ }
+
+ function rgbaToOpacity(colorString) {
+ const colorArray = colorString.replace(/[^\d,.]/g, '').split(',');
+ return colorArray[3];
+ }
+
+ function stringToRgba(colorString, opacity) {
+ if (typeof colorString === 'string') {
+ if (colorString.toLowerCase().startsWith('rgba(')) { return colorString; }
+ if (colorString.startsWith('#')) {
+ return hexToRgba(colorString, opacity || 1);
+ } else if (colorString.toLowerCase().startsWith('rgb(')) {
+ return rgbToRgba(colorString, opacity || 1);
+ }
+ }
+ return rgbToRgba(swDefaults.fillColor, swDefaults.fillOpacity);
+ }
+
+ function setFillColor(color) {
+ swStyle.fillColor = rgbToRgba(color, swStyle.fillOpacity);
+ }
+
+ function setStrokeColor(color) {
+ swStyle.strokeColor = rgbToRgba(color, swStyle.strokeOpacity);
+ }
+
+ function getStyleObject(feature, selected = false) {
+ let geometryType = feature.getGeometry().getType();
+ let styleObject = {};
+ if (feature.get(annotationField)) {
+ geometryType = 'TextPoint';
+ }
+ switch (geometryType) {
+ case 'LineString':
+ case 'MultiLineString':
+ styleObject = {
+ strokeColor: rgbToRgba(swStyle.strokeColor, swStyle.strokeOpacity),
+ strokeWidth: swStyle.strokeWidth,
+ strokeType: swStyle.strokeType,
+ showMeasureSegments: swStyle.showMeasureSegments,
+ showMeasure: swStyle.showMeasure,
+ selected
+ };
+ break;
+ case 'Polygon':
+ case 'MultiPolygon':
+ styleObject = {
+ fillColor: rgbToRgba(swStyle.fillColor, swStyle.fillOpacity),
+ strokeColor: rgbToRgba(swStyle.strokeColor, swStyle.strokeOpacity),
+ strokeWidth: swStyle.strokeWidth,
+ strokeType: swStyle.strokeType,
+ showMeasureSegments: swStyle.showMeasureSegments,
+ showMeasure: swStyle.showMeasure,
+ selected
+ };
+ break;
+ case 'Point':
+ case 'MultiPoint':
+ styleObject = {
+ fillColor: rgbToRgba(swStyle.fillColor, swStyle.fillOpacity),
+ strokeColor: rgbToRgba(swStyle.strokeColor, swStyle.strokeOpacity),
+ strokeWidth: swStyle.strokeWidth,
+ strokeType: swStyle.strokeType,
+ pointSize: swStyle.pointSize,
+ pointType: swStyle.pointType,
+ selected
+ };
+ break;
+ case 'TextPoint':
+ styleObject = {
+ fillColor: rgbToRgba(swStyle.fillColor, swStyle.fillOpacity),
+ textSize: swStyle.textSize,
+ textString: swStyle.textString,
+ textFont: swStyle.textFont,
+ selected
+ };
+ break;
+ default:
+ styleObject = swStyle;
+ styleObject.fillColor = rgbToRgba(swStyle.fillColor, swStyle.fillOpacity);
+ break;
+ }
+ return Object.assign({}, styleObject);
+ }
+
+ function restoreStylewindow() {
+ document.getElementById('o-draw-style-fill').classList.remove('hidden');
+ document.getElementById('o-draw-style-stroke').classList.remove('hidden');
+ document.getElementById('o-draw-style-point').classList.remove('hidden');
+ document.getElementById('o-draw-style-text').classList.remove('hidden');
+ document.getElementById('o-draw-style-measure').classList.remove('hidden');
+ }
+
+ function updateStylewindow(feature) {
+ const featureStyle = feature.get('origostyle') || {};
+ featureStyle.fillColor = stringToRgba(featureStyle.fillColor, featureStyle.fillOpacity);
+ featureStyle.strokeColor = stringToRgba(featureStyle.strokeColor, featureStyle.strokeOpacity);
+ let geometryType = feature.getGeometry().getType();
+ swStyle = Object.assign({}, swStyle, featureStyle);
+ if (feature.get(annotationField)) {
+ geometryType = 'TextPoint';
+ }
+ switch (geometryType) {
+ case 'LineString':
+ case 'MultiLineString':
+ document.getElementById('o-draw-style-fill').classList.add('hidden');
+ document.getElementById('o-draw-style-point').classList.add('hidden');
+ document.getElementById('o-draw-style-text').classList.add('hidden');
+ break;
+ case 'Polygon':
+ case 'MultiPolygon':
+ document.getElementById('o-draw-style-point').classList.add('hidden');
+ document.getElementById('o-draw-style-text').classList.add('hidden');
+ break;
+ case 'Point':
+ case 'MultiPoint':
+ document.getElementById('o-draw-style-text').classList.add('hidden');
+ document.getElementById('o-draw-style-measure').classList.add('hidden');
+ break;
+ case 'TextPoint':
+ document.getElementById('o-draw-style-stroke').classList.add('hidden');
+ document.getElementById('o-draw-style-point').classList.add('hidden');
+ document.getElementById('o-draw-style-measure').classList.add('hidden');
+ break;
+ default:
+ break;
+ }
+ document.getElementById('o-draw-style-pointSizeSlider').value = swStyle.pointSize;
+ document.getElementById('o-draw-style-pointType').value = swStyle.pointType;
+ document.getElementById('o-draw-style-textSizeSlider').value = swStyle.textSize;
+ document.getElementById('o-draw-style-textString').value = swStyle.textString;
+ swStyle.strokeOpacity = rgbaToOpacity(swStyle.strokeColor);
+ swStyle.strokeColor = rgbaToRgb(swStyle.strokeColor);
+ const strokeEl = document.getElementById('o-draw-style-strokeColor');
+ const strokeInputEl = strokeEl.querySelector(`input[value = "${swStyle.strokeColor}"]`);
+ if (strokeInputEl) {
+ strokeInputEl.checked = true;
+ } else {
+ const checkedEl = document.querySelector('input[name = "strokeColorRadio"]:checked');
+ if (checkedEl) {
+ checkedEl.checked = false;
+ }
+ }
+ document.getElementById('o-draw-style-strokeWidthSlider').value = swStyle.strokeWidth;
+ document.getElementById('o-draw-style-strokeOpacitySlider').value = swStyle.strokeOpacity;
+ document.getElementById('o-draw-style-strokeType').value = swStyle.strokeType;
+
+ const fillEl = document.getElementById('o-draw-style-fillColor');
+ swStyle.fillOpacity = rgbaToOpacity(swStyle.fillColor);
+ swStyle.fillColor = rgbaToRgb(swStyle.fillColor);
+ const fillInputEl = fillEl.querySelector(`input[value = "${swStyle.fillColor}"]`);
+ if (fillInputEl) {
+ fillInputEl.checked = true;
+ } else {
+ const checkedEl = document.querySelector('input[name = "fillColorRadio"]:checked');
+ if (checkedEl) {
+ checkedEl.checked = false;
+ }
+ }
+ document.getElementById('o-draw-style-fillOpacitySlider').value = swStyle.fillOpacity;
+ document.getElementById('o-draw-style-showMeasure').checked = swStyle.showMeasure;
+ document.getElementById('o-draw-style-showMeasureSegments').checked = swStyle.showMeasureSegments;
+ }
+
+ function getStyleFunction(feature, inputStyle = {}, projection = mapProjection) {
+ if (!feature.get('origostyle') && feature.get('style') && typeof feature.get('style') === 'object') {
+ feature.set('origostyle', feature.get('style'));
+ }
+ const featureStyle = feature.get('origostyle') || {};
+ const styleScale = feature.get('styleScale') || 1;
+ const newStyleObj = Object.assign({}, swDefaults, featureStyle, inputStyle);
+ newStyleObj.fillColor = stringToRgba(newStyleObj.fillColor, newStyleObj.fillOpacity);
+ newStyleObj.strokeColor = stringToRgba(newStyleObj.strokeColor, newStyleObj.strokeOpacity);
+ newStyleObj.strokeWidth *= styleScale;
+ newStyleObj.textSize *= styleScale;
+ newStyleObj.pointSize *= styleScale;
+ const geom = feature.getGeometry();
+ let geometryType = feature.getGeometry().getType();
+ if (feature.get(annotationField)) {
+ geometryType = 'TextPoint';
+ }
+ let style = [];
+ let lineDash;
+ if (newStyleObj.strokeType === 'dash') {
+ lineDash = [3 * newStyleObj.strokeWidth, 3 * newStyleObj.strokeWidth];
+ } else if (newStyleObj.strokeType === 'dash-point') {
+ lineDash = [3 * newStyleObj.strokeWidth, 3 * newStyleObj.strokeWidth, 0.1, 3 * newStyleObj.strokeWidth];
+ } else if (newStyleObj.strokeType === 'point') {
+ lineDash = [0.1, 3 * newStyleObj.strokeWidth];
+ } else {
+ lineDash = false;
+ }
+
+ const stroke = new Stroke({
+ color: newStyleObj.strokeColor,
+ width: newStyleObj.strokeWidth,
+ lineDash
+ });
+ const fill = new Fill({
+ color: newStyleObj.fillColor
+ });
+ const font = `${newStyleObj.textSize}px ${newStyleObj.textFont}`;
+ switch (geometryType) {
+ case 'LineString':
+ case 'MultiLineString':
+ style[0] = new Style({
+ stroke
+ });
+ if (newStyleObj.showMeasureSegments) {
+ const segmentLabelStyle = drawStyles.getSegmentLabelStyle(geom, projection, styleScale);
+ style = style.concat(segmentLabelStyle);
+ }
+ if (newStyleObj.showMeasure) {
+ const label = drawStyles.formatLength(geom, projection);
+ const point = new Point(geom.getLastCoordinate());
+ const labelStyle = drawStyles.getLabelStyle(styleScale);
+ labelStyle.setGeometry(point);
+ labelStyle.getText().setText(label);
+ style = style.concat(labelStyle);
+ }
+ break;
+ case 'Polygon':
+ case 'MultiPolygon':
+ style[0] = new Style({
+ fill,
+ stroke
+ });
+ if (newStyleObj.showMeasureSegments) {
+ const line = new LineString(geom.getCoordinates()[0]);
+ const segmentLabelStyle = drawStyles.getSegmentLabelStyle(line, projection, styleScale);
+ style = style.concat(segmentLabelStyle);
+ }
+ if (newStyleObj.showMeasure) {
+ const label = drawStyles.formatArea(geom, true, projection);
+ const point = geom.getInteriorPoint();
+ const labelStyle = drawStyles.getLabelStyle(styleScale);
+ labelStyle.setGeometry(point);
+ labelStyle.getText().setText(label);
+ style = style.concat(labelStyle);
+ }
+ break;
+ case 'Point':
+ case 'MultiPoint':
+ style[0] = drawStyles.createRegularShape(newStyleObj.pointType, newStyleObj.pointSize, fill, stroke);
+ break;
+ case 'TextPoint':
+ style[0] = new Style({
+ text: new Text({
+ text: newStyleObj.textString || 'Text',
+ font,
+ fill
+ })
+ });
+ feature.set(annotationField, newStyleObj.textString || 'Text');
+ break;
+ default:
+ style[0] = drawStyles.createRegularShape(newStyleObj.pointType, newStyleObj.pointSize, fill, stroke);
+ break;
+ }
+ if (newStyleObj.selected) {
+ style.push(drawStyles.selectionStyle);
+ }
+ return style;
+ }
+
+ function getSelectedFeatures() {
+ let features = [];
+
+ viewer.getMap().getInteractions().forEach((interaction) => {
+ if (interaction instanceof Select) {
+ features = interaction.getFeatures();
+ }
+ });
+
+ return features;
+ }
+
+ function styleFeature(feature, selected = false) {
+ const styleObject = getStyleObject(feature, selected);
+ feature.set('origostyle', styleObject);
+ }
+
+ function styleSelectedFeatures() {
+ getSelectedFeatures().forEach((feature) => {
+ styleFeature(feature, true);
+ });
+ }
+
+ function bindUIActions() {
+ let matches;
+ const fillColorEl = document.getElementById('o-draw-style-fillColor');
+ const strokeColorEl = document.getElementById('o-draw-style-strokeColor');
+
+ matches = fillColorEl.querySelectorAll('span');
+ for (let i = 0; i < matches.length; i += 1) {
+ matches[i].addEventListener('click', function e() {
+ setFillColor(this.style.backgroundColor);
+ styleSelectedFeatures();
+ });
+ }
+
+ matches = strokeColorEl.querySelectorAll('span');
+ for (let i = 0; i < matches.length; i += 1) {
+ matches[i].addEventListener('click', function e() {
+ setStrokeColor(this.style.backgroundColor);
+ styleSelectedFeatures();
+ });
+ }
+
+ document.getElementById('o-draw-style-fillOpacitySlider').addEventListener('input', function e() {
+ swStyle.fillOpacity = escapeQuotes(this.value);
+ setFillColor(swStyle.fillColor);
+ styleSelectedFeatures();
+ });
+
+ document.getElementById('o-draw-style-strokeOpacitySlider').addEventListener('input', function e() {
+ swStyle.strokeOpacity = escapeQuotes(this.value);
+ setStrokeColor(swStyle.strokeColor);
+ styleSelectedFeatures();
+ });
+
+ document.getElementById('o-draw-style-strokeWidthSlider').addEventListener('input', function e() {
+ swStyle.strokeWidth = escapeQuotes(this.value);
+ styleSelectedFeatures();
+ });
+
+ document.getElementById('o-draw-style-strokeType').addEventListener('change', function e() {
+ swStyle.strokeType = escapeQuotes(this.value);
+ styleSelectedFeatures();
+ });
+
+ document.getElementById('o-draw-style-pointType').addEventListener('change', function e() {
+ swStyle.pointType = escapeQuotes(this.value);
+ styleSelectedFeatures();
+ });
+
+ document.getElementById('o-draw-style-showMeasure').addEventListener('change', function e() {
+ swStyle.showMeasure = this.checked;
+ styleSelectedFeatures();
+ });
+
+ document.getElementById('o-draw-style-showMeasureSegments').addEventListener('change', function e() {
+ swStyle.showMeasureSegments = this.checked;
+ styleSelectedFeatures();
+ });
+
+ document.getElementById('o-draw-style-pointSizeSlider').addEventListener('input', function e() {
+ swStyle.pointSize = escapeQuotes(this.value);
+ styleSelectedFeatures();
+ });
+
+ document.getElementById('o-draw-style-textString').addEventListener('input', function e() {
+ swStyle.textString = escapeQuotes(this.value);
+ styleSelectedFeatures();
+ });
+
+ document.getElementById('o-draw-style-textSizeSlider').addEventListener('input', function e() {
+ swStyle.textSize = escapeQuotes(this.value);
+ styleSelectedFeatures();
+ });
+ }
+
+ annotationField = optOptions.annotation || 'annotation';
+ swStyle = Object.assign(swDefaults, optOptions.swDefaults);
+
+ let stylewindowEl;
+ let titleEl;
+ let headerEl;
+ let contentEl;
+ let closeButton;
+
+ palette.forEach((item, index) => {
+ const colorArr = rgbToArray(palette[index]);
+ palette[index] = `rgb(${colorArr[0]},${colorArr[1]},${colorArr[2]})`;
+ });
+
+ const closeWindow = function closeWindow() {
+ stylewindowEl.classList.add('hidden');
+ };
+
+ return Component({
+ closeWindow,
+ getStyleFunction,
+ getStyleObject,
+ restoreStylewindow,
+ updateStylewindow,
+ onInit() {
+ mapProjection = viewer.getProjection().getCode();
+ const headerCmps = [];
+ const thisComponent = this;
+ titleEl = Element({
+ cls: 'flex row justify-start margin-y-small margin-left text-weight-bold',
+ style: 'width: 100%;',
+ innerHTML: `${title}`
+ });
+ headerCmps.push(titleEl);
+
+ closeButton = Button({
+ cls: 'small round margin-top-small margin-right-small margin-bottom-auto margin-right icon-smaller grey-lightest no-shrink',
+ icon: closeIcon,
+ validStates: ['initial', 'hidden'],
+ click() {
+ closeWindow();
+ thisComponent.dispatch('showStylewindow', false);
+ }
+ });
+ headerCmps.push(closeButton);
+
+ headerEl = Element({
+ cls: 'flex justify-end grey-lightest',
+ components: headerCmps
+ });
+
+ contentEl = Element({
+ cls: 'o-draw-stylewindow-content overflow-auto',
+ innerHTML: `${styleTemplate(palette, swStyle)}`
+ });
+
+ this.addComponent(headerEl);
+ this.addComponent(contentEl);
+
+ this.on('render', this.onRender);
+ const target = optOptions.target || viewer.getId();
+ document.getElementById(target).appendChild(dom.html(this.render()));
+ this.dispatch('render');
+ bindUIActions();
+ },
+ onRender() {
+ stylewindowEl = document.getElementById(this.getId());
+ },
+ render() {
+ let addStyle;
+ if (css !== '') {
+ addStyle = `style="${css}"`;
+ } else {
+ addStyle = '';
+ }
+ return `
+
+ ${headerEl.render()}
+ ${contentEl.render()}
+
+
`;
+ }
+ });
+};
+
+export default Stylewindow;
diff --git a/src/templates/featureinfotemplate.js b/src/templates/featureinfotemplate.js
index fc609107f..2b7d7466b 100644
--- a/src/templates/featureinfotemplate.js
+++ b/src/templates/featureinfotemplate.js
@@ -1,6 +1,6 @@
import helpers from '../utils/templatehelpers';
export default (properties) => {
- const els = `${helpers.each(properties, obj => `${obj.prop} : ${obj.value}`)}`;
+ const els = `${helpers.each(properties, obj => `${obj.prop}: ${obj.value}`)}`;
return els;
};
diff --git a/src/ui/input.js b/src/ui/input.js
index b8152664c..fda7c4634 100644
--- a/src/ui/input.js
+++ b/src/ui/input.js
@@ -17,11 +17,16 @@ export default function Input(options = {}) {
onRender() {
const el = document.getElementById(this.getId());
el.addEventListener('keyup', this.onChange.bind(this));
+ el.addEventListener('focusout', this.onFocusOut.bind(this));
},
onChange(evt) {
value = evt.target.value;
this.dispatch('change', { value });
},
+ onFocusOut(evt) {
+ value = evt.target.value;
+ this.dispatch('focusout', { value });
+ },
render() {
return `
diff --git a/src/ui/modal.js b/src/ui/modal.js
index 8bca8993c..3c01c1e53 100644
--- a/src/ui/modal.js
+++ b/src/ui/modal.js
@@ -22,6 +22,7 @@ export default function Modal(options = {}) {
title = '',
content = '',
contentElement,
+ contentCmp,
cls = '',
isStatic = options.static,
target,
@@ -95,11 +96,13 @@ export default function Modal(options = {}) {
cls: 'flex row justify-end grey-lightest',
components: headerCmps
});
-
- contentEl = Element({
- cls: 'o-modal-content',
- innerHTML: `${content}`
- });
+ const elOptions = { cls: 'o-modal-content' };
+ if (contentCmp) {
+ elOptions.components = [contentCmp];
+ } else if (content) {
+ elOptions.innerHTML = `${content}`;
+ }
+ contentEl = Element(elOptions);
this.addComponent(screenEl);
this.addComponent(headerEl);
diff --git a/src/utils/escapequotes.js b/src/utils/escapequotes.js
new file mode 100644
index 000000000..744923a53
--- /dev/null
+++ b/src/utils/escapequotes.js
@@ -0,0 +1,3 @@
+export default function escapeQuotes(s) {
+ return s.replace(/'/g, "''");
+}
diff --git a/src/utils/exporttofile.js b/src/utils/exporttofile.js
index 79129785a..51c81efb9 100644
--- a/src/utils/exporttofile.js
+++ b/src/utils/exporttofile.js
@@ -42,6 +42,15 @@ const exportToFile = function exportToFile(features, format, opts = {}) {
featureProjection
};
+ // Set selected attribute if origostyle is present
+ features.forEach((feature) => {
+ if (feature.get('origostyle')) {
+ const style = feature.get('origostyle');
+ style.selected = false;
+ feature.set('origostyle', style);
+ }
+ });
+
// Convert features to the specified format using the provided parameters
const bytes = formatter.writeFeatures(features, formatterOptions);
diff --git a/src/utils/templatehelpers.js b/src/utils/templatehelpers.js
index 60944a26a..4b523b672 100644
--- a/src/utils/templatehelpers.js
+++ b/src/utils/templatehelpers.js
@@ -4,7 +4,7 @@ const templateHelpers = {
const props = Object.keys(obj);
const propsIncluded = [];
props.forEach(element => {
- if (typeof obj[element] !== 'undefined' && obj[element] !== null && obj[element] !== '') {
+ if (typeof obj[element] !== 'undefined' && obj[element] !== null && obj[element] !== '' && element !== 'style') {
propsIncluded.push(element);
}
});
diff --git a/src/utils/validate.js b/src/utils/validate.js
index 31e5eca85..3e2a06bb7 100644
--- a/src/utils/validate.js
+++ b/src/utils/validate.js
@@ -86,4 +86,13 @@ validate.color = (color) => {
return false;
};
+validate.json = (str) => {
+ try {
+ JSON.parse(str);
+ } catch (e) {
+ return false;
+ }
+ return true;
+};
+
export default validate;
diff --git a/src/viewer.js b/src/viewer.js
index 0ba04e1ac..7a0cd6837 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -17,13 +17,16 @@ import CenterMarker from './components/centermarker';
import flattenGroups from './utils/flattengroups';
import getcenter from './geometry/getcenter';
import isEmbedded from './utils/isembedded';
+import generateUUID from './utils/generateuuid';
import permalink from './permalink/permalink';
+import Stylewindow from './style/stylewindow';
const Viewer = function Viewer(targetOption, options = {}) {
let map;
let tileGrid;
let featureinfo;
let selectionmanager;
+ let stylewindow;
const {
breakPoints,
@@ -51,7 +54,8 @@ const Viewer = function Viewer(targetOption, options = {}) {
source = {},
clusterOptions = {},
tileGridOptions = {},
- url
+ url,
+ palette
} = options;
let {
@@ -138,6 +142,8 @@ const Viewer = function Viewer(targetOption, options = {}) {
const getSelectionManager = () => selectionmanager;
+ const getStylewindow = () => stylewindow;
+
const getCenter = () => getcenter;
const getMapUtils = () => maputils;
@@ -517,6 +523,7 @@ const Viewer = function Viewer(targetOption, options = {}) {
}));
tileGrid = maputils.tileGrid(tileGridSettings);
+ stylewindow = Stylewindow({ palette, viewer: this });
setMap(Map(Object.assign(options, { projection, center, zoom, target: this.getId() })));
@@ -677,8 +684,10 @@ const Viewer = function Viewer(targetOption, options = {}) {
setStyle,
zoomToExtent,
getSelectionManager,
+ getStylewindow,
getEmbedded,
permalink,
+ generateUUID,
centerMarker
});
};