UNPKG

tchen-vuelayers

Version:

Web map Vue components with the power of OpenLayers

287 lines (273 loc) 6.13 kB
import { distinctUntilChanged, map as mapObs, throttleTime } from 'rxjs/operators' import { boundingExtent } from '../ol-ext/extent' import { findPointOnSurface } from '../ol-ext/geom' import { transforms } from '../ol-ext/proj' import observableFromOlEvent from '../rx-ext/from-ol-event' import { hasGeometry } from '../util/assert' import { isEqual } from '../util/minilo' import mergeDescriptors from '../util/multi-merge-descriptors' import cmp from './ol-virt-cmp' import projTransforms from './proj-transforms' import useMapCmp from './use-map-cmp' const props = { /** * Coordinates in the map view projection. * @type {number[]|Coordinate} */ coordinates: { type: Array, required: true, validator: val => val.length, }, } const computed = { /** * @type {string} * @abstract * @readonly */ type () { throw new Error('Not implemented computed property') }, /** * @type {number[]|Extent|undefined} */ extent () { if (this.extentViewProj && this.resolvedDataProjection) { return this.extentToDataProj(this.extentViewProj) } }, /** * @type {number[]|Extent|undefined} */ extentViewProj () { if (this.rev && this.$geometry) { return this.$geometry.getExtent() } }, /** * @type {number[]|Coordinate|undefined} */ point () { if (this.pointViewProj && this.resolvedDataProjection) { return this.pointToDataProj(this.pointViewProj) } }, /** * @type {Array<number>} */ pointViewProj () { if (this.rev && this.$geometry) { return findPointOnSurface(this.$geometry) } }, /** * @type {Array|undefined} */ coordinatesViewProj () { if (this.rev && this.$geometry) { return this.$geometry.getCoordinates() } }, } const methods = { /** * @return {Geometry|Promise<Geometry>} * @protected */ createOlObject () { return this.createGeometry() }, /** * @return {Geometry|Promise<Geometry>} * @protected * @abstract */ createGeometry () { throw new Error('Not implemented method') }, /** * @return {Coordinate} */ getCoordinates () { hasGeometry(this) return this.toDataProj(this.$geometry.getCoordinates()) }, /** * @param {Coordinate} coordinates */ setCoordinates (coordinates) { hasGeometry(this) this.$geometry.setCoordinates(this.toViewProj(coordinates)) }, /** * @return {Promise} * @throws {AssertionError} * @protected */ init () { this.setupTransformFunctions() return this::cmp.methods.init() }, /** * @protected */ setupTransformFunctions () { // define helper methods based on geometry type const { transform } = transforms[this.type] /** * @method * @param {Array} coordinates * @return {number[]} * @protected */ this.toDataProj = coordinates => transform(coordinates, this.viewProjection, this.resolvedDataProjection) /** * @method * @param {Array} coordinates * @return {number[]} * @protected */ this.toViewProj = coordinates => transform(coordinates, this.resolvedDataProjection, this.viewProjection) }, /** * @return {void|Promise<void>} * @protected */ deinit () { return this::cmp.methods.deinit() }, /** * @return {Promise} */ refresh () { return this::cmp.methods.refresh() }, /** * @return {Object} * @protected */ getServices () { const vm = this return mergeDescriptors(this::cmp.methods.getServices(), { get geometry () { return vm.$geometry }, }) }, /** * @return {void} * @protected */ mount () { this.$geometryContainer && this.$geometryContainer.setGeometry(this) this.subscribeAll() }, /** * @return {void} * @protected */ unmount () { this.unsubscribeAll() this.$geometryContainer && this.$geometryContainer.setGeometry(undefined) }, /** * @return {void} * @protected */ subscribeAll () { this::subscribeToGeomChanges() }, } const watch = { coordinates (value) { if (!this.$geometry || !this.$view) return // compares in data projection let isEq = isEqualGeom({ coordinates: value, extent: boundingExtent(value), }, { coordinates: this.getCoordinates(), extent: this.extent, }) if (!isEq) { this.setCoordinates(value) } }, resolvedDataProjection () { if (this.$geometry) { this.setupTransformFunctions() this.setCoordinates(this.coordinates) } }, } export default { mixins: [cmp, useMapCmp, projTransforms], props, computed, watch, methods, stubVNode: { empty () { return this.$options.name }, }, created () { Object.defineProperties(this, { /** * @type {Geometry|undefined} */ $geometry: { enumerable: true, get: () => this.$olObject, }, $map: { enumerable: true, get: () => this.$services && this.$services.map, }, $view: { enumerable: true, get: () => this.$services && this.$services.view, }, $geometryContainer: { enumerable: true, get: () => this.$services && this.$services.geometryContainer, }, }) }, } /** * @return {void} * @private */ function subscribeToGeomChanges () { hasGeometry(this) const ft = 100 const changes = observableFromOlEvent( this.$geometry, 'change', () => ({ coordinates: this.getCoordinates(), extent: this.extent, }), ).pipe( throttleTime(ft), distinctUntilChanged(isEqualGeom), mapObs(({ coordinates }) => ({ prop: 'coordinates', value: coordinates, })), ) this.subscribeTo(changes, ({ prop, value }) => { ++this.rev this.$emit(`update:${prop}`, value) }) } /** * @param {{coordinates: number[], extent: number[]}} a * @param {{coordinates: number[], extent: number[]}} b * @returns {boolean} */ function isEqualGeom (a, b) { return isEqual(a.extent, b.extent) ? isEqual(a.coordinates, b.coordinates) : false }