UNPKG

tchen-vuelayers

Version:

Web map Vue components with the power of OpenLayers

484 lines (414 loc) 12.6 kB
/** * VueLayers * Web map Vue components with the power of OpenLayers * * @package vuelayers * @author Vladimir Vershinin <ghettovoice@gmail.com> * @version 0.11.1 * @license MIT * @copyright (c) 2017-2019, Vladimir Vershinin <ghettovoice@gmail.com> */ import _Object$defineProperties from '@babel/runtime-corejs2/core-js/object/define-properties'; import _objectSpread from '@babel/runtime-corejs2/helpers/esm/objectSpread'; import _Promise from '@babel/runtime-corejs2/core-js/promise'; import { never, shiftKeyOnly, singleClick } from 'ol/events/condition'; import Feature from 'ol/Feature'; import SelectInteraction from 'ol/interaction/Select'; import Vue from 'vue'; import interaction from '../mixin/interaction'; import projTransforms from '../mixin/proj-transforms'; import stylesContainer from '../mixin/styles-container'; import { getFeatureId } from '../ol-ext/feature'; import { createStyle, defaultEditStyle } from '../ol-ext/style'; import observableFromOlEvent from '../rx-ext/from-ol-event'; import { hasInteraction, hasMap } from '../util/assert'; import { constant, difference, forEach, isFunction, mapValues, stubArray, pick } from '../util/minilo'; import mergeDescriptors from '../util/multi-merge-descriptors'; import { makeWatchers } from '../util/vue-helpers'; import _Object$assign from '@babel/runtime-corejs2/core-js/object/assign'; /** * @vueProps */ var props = { /** * A function that takes an `ol.Feature` and an `ol.layer.Layer` and returns `true` if the feature may be selected or `false` otherwise. * @type {function|undefined} */ filter: { type: Function, default: constant(true) }, /** * Hit-detection tolerance. Pixels inside the radius around the given position will be checked for features. * This only works for the canvas renderer and not for WebGL. * @type {number} */ hitTolerance: { type: Number, default: 0 }, /** * A boolean that determines if the default behaviour should select only single features or all (overlapping) * features at the clicked map position. * @type {boolean} */ multi: { type: Boolean, default: false }, /** * Selected features as array of GeoJSON features with coordinates in the map view projection. * @type {string[]|number[]|Object[]} */ features: { type: Array, default: stubArray }, /** * Wrap the world horizontally on the selection overlay. * @type {boolean} */ wrapX: { type: Boolean, default: true }, /** * A function that takes an `ol.MapBrowserEvent` and returns a boolean to indicate whether that event should * be handled. By default, this is `ol.events.condition.never`. Use this if you want to use different events * for `add` and `remove` instead of `toggle`. * @type {function|undefined} */ addCondition: { type: Function, default: never }, /** * A function that takes an `ol.MapBrowserEvent` and returns a boolean to indicate whether that event should be handled. * This is the event for the selected features as a whole. By default, this is `ol.events.condition.singleClick`. * Clicking on a feature selects that feature and removes any that were in the selection. Clicking outside any feature * removes all from the selection. * @type {function|undefined} */ condition: { type: Function, default: singleClick }, /** * A function that takes an `ol.MapBrowserEvent` and returns a boolean to indicate whether that event should be handled. * By default, this is `ol.events.condition.never`. Use this if you want to use different events for `add` and `remove` * instead of `toggle`. * @type {function|undefined} */ removeCondition: { type: Function, default: never }, /** * A function that takes an `ol.MapBrowserEvent` and returns a boolean to indicate whether that event should be handled. * This is in addition to the `condition` event. By default, `ol.events.condition.shiftKeyOnly`, i.e. pressing `shift` * as well as the `condition` event, adds that feature to the current selection if it is not currently selected, * and removes it if it is. * @type {function|undefined} */ toggleCondition: { type: Function, default: shiftKeyOnly } /** * @vueComputed */ }; var computed = {}; /** * @vueMethods */ var methods = { /** * @return {Select} * @protected */ createInteraction: function createInteraction() { return new SelectInteraction({ multi: this.multi, wrapX: this.wrapX, filter: this.filter, style: this.createStyleFunc(), addCondition: this.addCondition, condition: this.condition, removeCondition: this.removeCondition, toggleCondition: this.toggleCondition }); }, /** * @return {function(feature: Feature): Style} * @protected */ getDefaultStyles: function getDefaultStyles() { var defaultStyles = mapValues(defaultEditStyle(), function (styles) { return styles.map(createStyle); }); return function __selectDefaultStyleFunc(feature) { if (feature.getGeometry()) { return defaultStyles[feature.getGeometry().getType()]; } }; }, /** * @return {Feature[]} */ getFeatures: function getFeatures() { return this.$interaction && this.$interaction.getFeatures().getArray() || []; }, /** * @returns {Object} * @protected */ getServices: function getServices() { return mergeDescriptors(interaction.methods.getServices.call(this), stylesContainer.methods.getServices.call(this)); }, /** * @return {Interaction|undefined} * @protected */ getStyleTarget: function getStyleTarget() { return this.$interaction; }, /** * @return {void} * @protected */ mount: function mount() { interaction.methods.mount.call(this); this.features.forEach(this.select); }, /** * @return {void} * @protected */ unmount: function unmount() { this.unselectAll(); interaction.methods.unmount.call(this); }, /** * @param {Object|Vue|Feature|string|number} feature * @return {void} * @throws {Error} */ select: function select(feature) { hasMap(this); hasInteraction(this); var id = getFeatureId(feature); if (!id) { throw new Error('Undefined feature id'); } if (feature instanceof Vue) { feature = feature.$feature; } var selectedIds = this.$features.map(getFeatureId); if (selectedIds.includes(id)) return; if (!(feature instanceof Feature)) { feature = undefined; forEach(this.$map.getLayers().getArray(), function (layer) { var source = layer.getSource(); if (source && isFunction(source.getFeatureById)) { feature = source.getFeatureById(id); } return !feature; }); } feature && this.$interaction.getFeatures().push(feature); }, /** * @param {Object|Vue|Feature|string|number} feature * @return {void} */ unselect: function unselect(feature) { hasInteraction(this); var id = getFeatureId(feature); if (!id) { throw new Error('Undefined feature id'); } if (feature instanceof Vue) { feature = feature.$feature; } var selectedIds = this.$features.map(getFeatureId); var idx = selectedIds.findIndex(function (x) { return x === id; }); if (idx !== -1) { this.$interaction.getFeatures().removeAt(idx); } }, /** * @param {Array<{style: Style, condition: (function|boolean|undefined)}>|function(feature: Feature): Style|Vue|undefined} styles * @return {void} * @protected */ setStyle: function setStyle(styles) { if (styles !== this._styles) { this._styles = styles; this.scheduleRefresh(); } }, /** * @return {Promise} */ refresh: function refresh() { var _this = this; return _Promise.all([new _Promise(function (resolve) { if (_this.$interaction) { var featuresCollection = _this.$interaction.getFeatures(); featuresCollection.once('change', function () { return resolve(); }); featuresCollection.changed(); } else { resolve(); } }), interaction.methods.refresh.call(this)]); }, /** * @return {void} * @protected */ subscribeAll: function subscribeAll() { subscribeToInteractionChanges.call(this); }, /** * Removes all features from selection. * @return {void} */ unselectAll: function unselectAll() { hasInteraction(this); this.$interaction.getFeatures().clear(); } }; var watch = _objectSpread({}, makeWatchers(['filter', 'hitTolerance', 'multi', 'wrapX', 'addCondition', 'condition', 'removeCondition', 'toggleCondition'], function () { return function () { this.scheduleRecreate(); }; }), { features: function features(value) { if (!this.$interaction) return; var diffById = function diffById(a, b) { return getFeatureId(a) === getFeatureId(b); }; var forSelect = difference(value, this.$features, diffById); var forUnselect = difference(this.$features, value, diffById); forSelect.forEach(this.select); forUnselect.forEach(this.unselect); } }); /** * @vueProto * @alias module:select-interaction/interaction * @title vl-interaction-select */ var script = { name: 'vl-interaction-select', mixins: [interaction, stylesContainer, projTransforms], props: props, computed: computed, methods: methods, watch: watch, stubVNode: { empty: false, attrs: function attrs() { return { class: this.$options.name }; } }, created: function created() { _Object$defineProperties(this, { $features: { enumerable: true, get: this.getFeatures } }); } }; /** * @return {void} * @private */ function subscribeToInteractionChanges() { var _this2 = this; hasInteraction(this); var events = observableFromOlEvent(this.$interaction, 'select'); this.subscribeTo(events, function (_ref) { var selected = _ref.selected, deselected = _ref.deselected, mapBrowserEvent = _ref.mapBrowserEvent; ++_this2.rev; deselected.forEach(function (feature) { return _this2.$emit('unselect', { feature: feature, mapBrowserEvent: mapBrowserEvent }); }); selected.forEach(function (feature) { return _this2.$emit('select', { feature: feature, mapBrowserEvent: mapBrowserEvent }); }); _this2.$emit('update:features', _this2.$features.map(_this2.writeFeatureInDataProj.bind(_this2))); }); } /* script */ var __vue_script__ = script; /* template */ var __vue_render__ = function __vue_render__() { var _vm = this; var _h = _vm.$createElement; var _c = _vm._self._c || _h; return _c('i', { class: [_vm.$options.name], staticStyle: { "display": "none !important" } }, [_vm._t("default", null, { features: _vm.features })], 2); }; var __vue_staticRenderFns__ = []; /* style */ var __vue_inject_styles__ = undefined; /* scoped */ var __vue_scope_id__ = undefined; /* module identifier */ var __vue_module_identifier__ = undefined; /* functional template */ var __vue_is_functional_template__ = false; /* component normalizer */ function __vue_normalize__(template, style, script$$1, scope, functional, moduleIdentifier, createInjector, createInjectorSSR) { var component = (typeof script$$1 === 'function' ? script$$1.options : script$$1) || {}; // For security concerns, we use only base name in production mode. component.__file = "interaction.vue"; if (!component.render) { component.render = template.render; component.staticRenderFns = template.staticRenderFns; component._compiled = true; if (functional) component.functional = true; } component._scopeId = scope; return component; } /* style inject */ /* style inject SSR */ var Interaction = __vue_normalize__({ render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ }, __vue_inject_styles__, __vue_script__, __vue_scope_id__, __vue_is_functional_template__, __vue_module_identifier__, undefined, undefined); function plugin(Vue$$1) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (plugin.installed) { return; } plugin.installed = true; options = pick(options, 'dataProjection'); _Object$assign(Interaction, options); Vue$$1.component(Interaction.name, Interaction); } export default plugin; export { Interaction, plugin as install };