diff --git a/docs/api/layer/layer.en.md b/docs/api/layer/layer.en.md index 2368b7eeb4..54aabf1e2b 100644 --- a/docs/api/layer/layer.en.md +++ b/docs/api/layer/layer.en.md @@ -238,7 +238,7 @@ layer.color('white'); // 指定颜色 - `colors`: string | array | function -colors 的参数有以下情况:  如果为空,即未指定颜色的数组,那么使用内置的全局的颜色;如果需要指定颜色,则需要以数组格式传入,那么分类的颜色按照数组中的颜色确定。对于颜色的分配顺序。 +colors 的参数有以下情况:  如果为空,即未指定颜色的数组,那么使用内置的全局的颜色;如果需要指定颜色,则需要以数组格式传入,那么分类的颜色按照数组中的颜色确定. ```javascript layer.color('name'); // 使用默认的颜色 diff --git a/docs/api/layer/layer.zh.md b/docs/api/layer/layer.zh.md index b4249c7fbe..b26f5e863c 100644 --- a/docs/api/layer/layer.zh.md +++ b/docs/api/layer/layer.zh.md @@ -238,7 +238,7 @@ layer.color('white'); // 指定颜色 - `colors`: string | array | function -colors 的参数有以下情况:  如果为空,即未指定颜色的数组,那么使用内置的全局的颜色;如果需要指定颜色,则需要以数组格式传入,那么分类的颜色按照数组中的颜色确定。对于颜色的分配顺序。 +colors 的参数有以下情况:  如果为空,即未指定颜色的数组,那么使用内置的全局的颜色;如果需要指定颜色,则需要以数组格式传入,那么分类的颜色按照数组中的颜色确定。 ```javascript layer.color('name'); // 使用默认的颜色 diff --git a/packages/component/src/control/zoom.ts b/packages/component/src/control/zoom.ts index abe8549633..bbb285c1a0 100644 --- a/packages/component/src/control/zoom.ts +++ b/packages/component/src/control/zoom.ts @@ -27,7 +27,7 @@ export default class Zoom extends Control { }; } - public onAdd() { + public onAdd(): HTMLElement { const zoomName = 'l7-control-zoom'; const container = DOM.create('div', zoomName + ' l7-bar'); @@ -93,6 +93,7 @@ export default class Zoom extends Control { ) { const link = DOM.create('a', className, container) as HTMLLinkElement; link.innerHTML = html; + link.title = tile; link.href = 'javascript:void(0)'; link.addEventListener('click', fn); return link; diff --git a/packages/core/src/services/config/ConfigService.ts b/packages/core/src/services/config/ConfigService.ts index c0454c1ac9..b5f6642490 100644 --- a/packages/core/src/services/config/ConfigService.ts +++ b/packages/core/src/services/config/ConfigService.ts @@ -4,8 +4,6 @@ import { merge } from 'lodash'; import { ILayerConfig } from '../layer/ILayerService'; import { IRenderConfig } from '../renderer/IRendererService'; import { IGlobalConfigService, ISceneConfig } from './IConfigService'; -import mapConfigSchema from './mapConfigSchema'; -import sceneConfigSchema from './sceneConfigSchema'; import WarnInfo, { IWarnInfo } from './warnInfo'; /** @@ -58,6 +56,8 @@ const defaultLayerConfig: Partial = { maxZoom: 24, visible: true, autoFit: false, + pickingBuffer: 0, + enablePropagation: false, zIndex: 0, blend: 'normal', pickedFeatureID: -1, diff --git a/packages/core/src/services/interaction/InteractionService.ts b/packages/core/src/services/interaction/InteractionService.ts index 3416ce94db..6489fdcaf3 100644 --- a/packages/core/src/services/interaction/InteractionService.ts +++ b/packages/core/src/services/interaction/InteractionService.ts @@ -1,6 +1,7 @@ import EventEmitter from 'eventemitter3'; import Hammer from 'hammerjs'; import { inject, injectable } from 'inversify'; +// @ts-ignore import { TYPES } from '../../types'; import { ILogService } from '../log/ILogService'; import { ILngLat, IMapService } from '../map/IMapService'; @@ -58,10 +59,20 @@ export default class InteractionService extends EventEmitter private addEventListenerOnMap() { const $containter = this.mapService.getMapContainer(); + Hammer.defaults.domEvents = true; if ($containter) { const hammertime = new Hammer.Manager($containter); - hammertime.add(new Hammer.Tap({ event: 'dblclick', taps: 2 })); - hammertime.add(new Hammer.Tap({ event: 'click' })); + hammertime.add( + new Hammer.Tap({ + event: 'dblclick', + taps: 2, + }), + ); + hammertime.add( + new Hammer.Tap({ + event: 'click', + }), + ); hammertime.add(new Hammer.Pan({ threshold: 0, pointers: 0 })); hammertime.add(new Hammer.Press({})); // hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL }); @@ -86,7 +97,7 @@ export default class InteractionService extends EventEmitter const $containter = this.mapService.getMapContainer(); if ($containter) { $containter.removeEventListener('mousemove', this.onHover); - this.hammertime.off('dblclick click', this.onHammer); + // this.hammertime.off('dblclick click', this.onHammer); this.hammertime.off('panstart panmove panend pancancel', this.onDrag); // $containter.removeEventListener('touchstart', this.onTouch); // $containter.removeEventListener('click', this.onHover); @@ -102,9 +113,19 @@ export default class InteractionService extends EventEmitter this.emit(InteractionEvent.Drag, interactionTarget); }; private onHammer = (target: HammerInput) => { + target.srcEvent.stopPropagation(); const interactionTarget = this.interactionEvent(target); this.emit(InteractionEvent.Hover, interactionTarget); }; + private onTouch = (target: TouchEvent) => { + const touch = target.touches[0]; + // @ts-ignore + this.onHover({ + x: touch.pageX, + y: touch.pageY, + type: 'touch', + }); + }; private interactionEvent(target: HammerInput) { const { type, pointerType } = target; @@ -147,7 +168,7 @@ export default class InteractionService extends EventEmitter this.isDoubleTap(x, y, lngLat); return; } - if (type !== 'click' || type !== 'click') { + if (type !== 'click' && type !== 'dblclick') { this.emit(InteractionEvent.Hover, { x, y, @@ -161,7 +182,7 @@ export default class InteractionService extends EventEmitter const nowTime = new Date().getTime(); let type = 'click'; if ( - nowTime - this.lastClickTime < 500 && + nowTime - this.lastClickTime < 400 && Math.abs(this.lastClickXY[0] - x) < 10 && Math.abs(this.lastClickXY[1] - y) < 10 ) { @@ -179,7 +200,7 @@ export default class InteractionService extends EventEmitter this.clickTimer = setTimeout(() => { type = 'click'; this.emit(InteractionEvent.Hover, { x, y, lngLat, type }); - }, 500); + }, 400); } } } diff --git a/packages/core/src/services/interaction/PickingService.ts b/packages/core/src/services/interaction/PickingService.ts index ced69db3c1..3503e38c86 100644 --- a/packages/core/src/services/interaction/PickingService.ts +++ b/packages/core/src/services/interaction/PickingService.ts @@ -84,20 +84,21 @@ export default class PickingService implements IPickingService { useFramebuffer(this.pickingFBO, () => { const layers = this.layerService.getLayers(); layers - .filter((layer) => layer.needPick()) + .filter((layer) => layer.needPick(target.type)) .reverse() - .forEach((layer) => { + .some((layer) => { clear({ framebuffer: this.pickingFBO, color: [0, 0, 0, 0], stencil: 0, depth: 1, }); - layer.hooks.beforePickingEncode.call(); layer.renderModels(); layer.hooks.afterPickingEncode.call(); - this.pickFromPickingFBO(layer, target); + const isPicked = this.pickFromPickingFBO(layer, target); + return isPicked && !layer.getLayerConfig().enablePropagation; + // return false; }); }); } @@ -105,6 +106,7 @@ export default class PickingService implements IPickingService { layer: ILayer, { x, y, lngLat, type }: IInteractionTarget, ) => { + let isPicked = false; const { getViewportSize, readPixels } = this.rendererService; const { width, height } = getViewportSize(); const { enableHighlight, enableSelect } = layer.getLayerConfig(); @@ -117,7 +119,7 @@ export default class PickingService implements IPickingService { yInDevicePixel > height - 1 * window.devicePixelRatio || yInDevicePixel < 0 ) { - return; + return false; } let pickedColors: Uint8Array | undefined; pickedColors = readPixels({ @@ -136,6 +138,13 @@ export default class PickingService implements IPickingService { ) { const pickedFeatureIdx = decodePickingColor(pickedColors); const rawFeature = layer.getSource().getFeatureById(pickedFeatureIdx); + if ( + pickedFeatureIdx !== layer.getCurrentPickId() && + type === 'mousemove' + ) { + type = 'mouseenter'; + } + const target = { x, y, @@ -150,15 +159,20 @@ export default class PickingService implements IPickingService { // ); } else { // trigger onHover/Click callback on layer + isPicked = true; layer.setCurrentPickId(pickedFeatureIdx); - this.triggerHoverOnLayer(layer, target); + this.triggerHoverOnLayer(layer, target); // 触发拾取事件 } } else { + // 未选中 const target = { x, y, lngLat, - type: layer.getCurrentPickId() === null ? 'un' + type : 'mouseout', + type: + layer.getCurrentPickId() !== null && type === 'mousemove' + ? 'mouseout' + : 'un' + type, featureId: null, feature: null, }; @@ -178,8 +192,19 @@ export default class PickingService implements IPickingService { type === 'click' && pickedColors?.toString() !== [0, 0, 0, 0].toString() ) { - this.selectFeature(layer, pickedColors); + const selectedId = decodePickingColor(pickedColors); + if ( + layer.getCurrentSelectedId() === null || + selectedId !== layer.getCurrentSelectedId() + ) { + this.selectFeature(layer, pickedColors); + layer.setCurrentSelectedId(selectedId); + } else { + this.selectFeature(layer, new Uint8Array([0, 0, 0, 0])); // toggle select + layer.setCurrentSelectedId(null); + } } + return isPicked; }; private triggerHoverOnLayer( layer: ILayer, diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index dde1cb7676..21b23ff79b 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -84,7 +84,7 @@ export interface ILayer { layerModelNeedUpdate: boolean; layerModel: ILayerModel; dataState: IDataState; // 数据流状态 - pickedFeatureID: number; + pickedFeatureID: number | null; hooks: { init: SyncBailHook; afterInit: SyncBailHook; @@ -106,12 +106,14 @@ export interface ILayer { options?: ISourceCFG; }; multiPassRenderer: IMultiPassRenderer; - needPick(): boolean; + needPick(type: string): boolean; getLayerConfig(): Partial; getContainer(): Container; setContainer(container: Container): void; setCurrentPickId(id: number | null): void; getCurrentPickId(): number | null; + setCurrentSelectedId(id: number | null): void; + getCurrentSelectedId(): number | null; prepareBuildModel(): void; renderModels(): void; buildModels(): void; @@ -221,6 +223,8 @@ export interface ILayerConfig { maxZoom: number; visible: boolean; zIndex: number; + pickingBuffer: number; + enablePropagation: boolean; autoFit: boolean; fitBoundsOptions?: unknown; name: string; // diff --git a/packages/core/src/services/map/IMapService.ts b/packages/core/src/services/map/IMapService.ts index 77382636c4..b4b8eb4e75 100644 --- a/packages/core/src/services/map/IMapService.ts +++ b/packages/core/src/services/map/IMapService.ts @@ -52,11 +52,12 @@ export interface IMapService { getRotation(): number; getBounds(): Bounds; getMapContainer(): HTMLElement | null; + getMapCanvasContainer(): HTMLElement; // control with raw map setRotation(rotation: number): void; - zoomIn(): void; - zoomOut(): void; + zoomIn(option?: any, eventData?: any): void; + zoomOut(option?: any, eventData?: any): void; panTo(p: Point): void; panBy(pixel: Point): void; fitBounds(bound: Bounds, fitBoundsOptions?: unknown): void; diff --git a/packages/core/src/services/renderer/passes/PixelPickingPass.ts b/packages/core/src/services/renderer/passes/PixelPickingPass.ts index bd93335ae4..2bf3f414d0 100644 --- a/packages/core/src/services/renderer/passes/PixelPickingPass.ts +++ b/packages/core/src/services/renderer/passes/PixelPickingPass.ts @@ -130,7 +130,7 @@ export default class PixelPickingPass< * TODO:支持区域拾取 */ private pickFromPickingFBO = ({ x, y, lngLat, type }: IInteractionTarget) => { - if (!this.layer.isVisible() || !this.layer.needPick()) { + if (!this.layer.isVisible() || !this.layer.needPick(type)) { return; } const { diff --git a/packages/core/src/shaders/picking.vert.glsl b/packages/core/src/shaders/picking.vert.glsl index 7b1f2c8b5a..be59485d7f 100644 --- a/packages/core/src/shaders/picking.vert.glsl +++ b/packages/core/src/shaders/picking.vert.glsl @@ -5,6 +5,7 @@ uniform vec3 u_PickingColor : [0, 0, 0]; uniform vec4 u_HighlightColor : [0, 0, 0, 0]; uniform float u_PickingStage : 0.0; uniform float u_PickingThreshold : 1.0; +uniform float u_PickingBuffer: 0.0; #define PICKING_NONE 0.0 #define PICKING_ENCODE 1.0 @@ -25,3 +26,7 @@ void setPickingColor(vec3 pickingColor) { // Stores the picking color so that the fragment shader can render it during picking v_PickingResult.rgb = pickingColor * COLOR_SCALE; } + +float setPickingSize(float x) { + return u_PickingStage == PICKING_ENCODE ? x + u_PickingBuffer : x; +} diff --git a/packages/draw/package.json b/packages/draw/package.json index 8aeb7f7e80..538300f2c1 100644 --- a/packages/draw/package.json +++ b/packages/draw/package.json @@ -33,6 +33,8 @@ "test": "jest" }, "dependencies": { + "@turf/midpoint": "^5.1.5", + "@antv/l7-component": "^2.1.9", "@antv/l7": "^2.1.9", "@babel/runtime": "^7.7.7", "@turf/circle": "^6.0.1", diff --git a/packages/draw/src/css/draw.less b/packages/draw/src/css/draw.less new file mode 100644 index 0000000000..3f7b6f9f5a --- /dev/null +++ b/packages/draw/src/css/draw.less @@ -0,0 +1,70 @@ +.l7-control-draw { + border-radius: 4px; + background: #fff; + box-shadow: 0 0 0 1px rgba(0,0,0,.1); + display: flex; + a { + background-repeat: no-repeat; + background-position: center; + background-size: 20px 20px; + width: 30px; + height: 30px; + display: block; + padding: 0; + outline: none; + border: 0; + margin: 1px; + box-sizing: border-box; + cursor: pointer; + } + a:not(:disabled):hover { + background-color: rgba(0, 0, 0, 0.05); + } + + a:focus { + box-shadow: 0 0 2px 2px #0096ff; + } + .draw-point { + background-image: url('https://gw.alipayobjects.com/zos/bmw-prod/f7bf0137-7de9-411a-9a37-4880acc58d29.svg'); + } + .draw-line { + background-image: url('https://gw.alipayobjects.com/zos/bmw-prod/a784e304-afb2-4888-91b4-e0d0e8058810.svg'); + } + .draw-polygon { + background-image: url('https://gw.alipayobjects.com/zos/bmw-prod/6dd9914d-d625-4424-8db7-19953e67444f.svg'); + } + .draw-rect { + background-image: url('https://gw.alipayobjects.com/zos/bmw-prod/fe578687-693b-4c27-afb3-0e675d9431ff.svg'); + } + .draw-circle { + background-image: url('https://gw.alipayobjects.com/zos/bmw-prod/fa234d68-85aa-4628-b1b6-aad47ab093b4.svg'); + } + .draw-delete { + background-image: url('https://gw.alipayobjects.com/zos/bmw-prod/0c9ba44b-aba3-4007-ba4e-ab815279886b.svg'); + } + +} +.horizontal { + flex-direction: row; + a:focus:first-child { + border-radius: 4px 0px 0px 4px; + } + a:focus:last-child { + border-radius: 0px 4px 4px 4px; + } + a+a { + border-left: 1px solid #eee; + } +} +.vertical { + flex-direction: column; + a:focus:first-child { + border-radius: 4px 4px 0 0; + } + a:focus:last-child { + border-radius: 0px 0px 4px 4px; + } + a+a { + border-top: 1px solid #eee; + } +} diff --git a/packages/draw/src/css/svg/combine.svg b/packages/draw/src/css/svg/combine.svg new file mode 100644 index 0000000000..febc04705d --- /dev/null +++ b/packages/draw/src/css/svg/combine.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/packages/draw/src/css/svg/line.svg b/packages/draw/src/css/svg/line.svg new file mode 100644 index 0000000000..9bcec18c9c --- /dev/null +++ b/packages/draw/src/css/svg/line.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/packages/draw/src/css/svg/point.svg b/packages/draw/src/css/svg/point.svg new file mode 100644 index 0000000000..c767a55718 --- /dev/null +++ b/packages/draw/src/css/svg/point.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/packages/draw/src/css/svg/polygon.svg b/packages/draw/src/css/svg/polygon.svg new file mode 100644 index 0000000000..4c8dc9bb4e --- /dev/null +++ b/packages/draw/src/css/svg/polygon.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/packages/draw/src/css/svg/trash.svg b/packages/draw/src/css/svg/trash.svg new file mode 100644 index 0000000000..8b350ad1e6 --- /dev/null +++ b/packages/draw/src/css/svg/trash.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/packages/draw/src/css/svg/uncombine.svg b/packages/draw/src/css/svg/uncombine.svg new file mode 100644 index 0000000000..3a4b3190cb --- /dev/null +++ b/packages/draw/src/css/svg/uncombine.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/packages/draw/src/draw_control.ts b/packages/draw/src/draw_control.ts new file mode 100644 index 0000000000..8d05858f4e --- /dev/null +++ b/packages/draw/src/draw_control.ts @@ -0,0 +1,142 @@ +import { Control, IControlOption, PositionType, Scene } from '@antv/l7'; +import { DOM } from '@antv/l7-utils'; +import './css/draw.less'; +import { + DrawCircle, + DrawDelete, + DrawFeature, + DrawLine, + DrawPoint, + DrawPolygon, + DrawRect, +} from './modes'; +import { DrawEvent, DrawModes } from './util/constant'; +export interface IControls { + [key: string]: boolean; +} + +export interface IDrawControlOption extends IControlOption { + pickBuffer: number; + controls: IControls; + layout: 'horizontal' | 'vertical'; +} +export class DrawControl extends Control { + private draw: { + [key: string]: DrawFeature; + } = {}; + private drawDelete: DrawDelete; + private currentDraw: DrawFeature; + private scene: Scene; + constructor(scene: Scene, options: Partial) { + super(options); + this.scene = scene; + } + + public getDefault() { + return { + position: PositionType.TOPLEFT, + controls: { + point: true, + }, + name: 'draw', + }; + } + + public onAdd(): HTMLElement { + const { layout } = this.controlOption; + const controlClass = 'l7-control-draw' + ' ' + layout; + const { controls } = this.controlOption as IDrawControlOption; + const container = DOM.create('div', controlClass) as HTMLElement; + if (controls.point) { + this.draw.point = new DrawPoint(this.scene); + this.createButton( + '绘制点', + 'draw-point', + container, + this.onButtonClick.bind(null, 'point'), + ); + } + if (controls.line) { + this.draw.line = new DrawLine(this.scene); + this.createButton( + '绘制线', + 'draw-line', + container, + this.onButtonClick.bind(null, 'line'), + ); + } + if (controls.polygon) { + this.createButton( + '绘制面', + 'draw-polygon', + container, + this.onButtonClick.bind(null, 'polygon'), + ); + this.draw.polygon = new DrawPolygon(this.scene); + } + if (controls.rect) { + this.draw.rect = new DrawRect(this.scene); + this.createButton( + '绘制矩形', + 'draw-rect', + container, + this.onButtonClick.bind(null, 'rect'), + ); + } + if (controls.circle) { + this.draw.circle = new DrawCircle(this.scene); + this.createButton( + '绘制圆', + 'draw-circle', + container, + this.onButtonClick.bind(null, 'circle'), + ); + } + + if (controls.delete) { + this.drawDelete = new DrawDelete(this.scene); + this.createButton( + '删除', + 'draw-delete', + container, + this.onDeleteMode.bind(null, 'delete'), + ); + } + // 监听组件 选中, 编辑 + return container; + } + + private createButton( + tile: string, + className: string, + container: HTMLElement, + fn: (...arg: any[]) => any, + ) { + const link = DOM.create('a', className, container) as HTMLLinkElement; + link.href = 'javascript:void(0)'; + link.title = tile; + link.addEventListener('mousedown', fn, true); + return link; + } + + private onButtonClick = (type: string, e: MouseEvent) => { + e.stopPropagation(); + for (const draw in this.draw) { + if (draw === type) { + this.draw[draw].enable(); + this.currentDraw = this.draw[draw]; + } else { + this.draw[draw].disable(); + } + } + }; + + private onDeleteMode = (type: string, e: MouseEvent) => { + e.stopPropagation(); + if (!this.currentDraw) { + return; + } + this.currentDraw.deleteMode.enable(); + return false; + }; +} diff --git a/packages/draw/src/index.ts b/packages/draw/src/index.ts index b97b258fd7..5e830027f5 100644 --- a/packages/draw/src/index.ts +++ b/packages/draw/src/index.ts @@ -1 +1,2 @@ export * from './modes/index'; +export * from './draw_control'; diff --git a/packages/draw/src/modes/draw_circle.ts b/packages/draw/src/modes/draw_circle.ts index 9a80080e44..fd39dd13fb 100644 --- a/packages/draw/src/modes/draw_circle.ts +++ b/packages/draw/src/modes/draw_circle.ts @@ -1,96 +1,130 @@ +import { IInteractionTarget, ILngLat, PointLayer, Scene } from '@antv/l7'; import { - IInteractionTarget, - ILayer, - ILngLat, - IPopup, - LineLayer, - PointLayer, - PolygonLayer, - Popup, - Scene, -} from '@antv/l7'; -import { Feature, FeatureCollection, point } from '@turf/helpers'; -import selectRender from '../render/selected'; + Feature, + FeatureCollection, + featureCollection, + Geometries, + Properties, +} from '@turf/helpers'; import { DrawEvent, DrawModes, unitsType } from '../util/constant'; -import { createCircle } from '../util/create_geometry'; -import moveFeatures, { movePoint, moveRing } from '../util/move_featrues'; +import { createCircle, createPoint } from '../util/create_geometry'; +import moveFeatures, { movePoint } from '../util/move_featrues'; import DrawFeature, { IDrawFeatureOption } from './draw_feature'; export interface IDrawRectOption extends IDrawFeatureOption { units: unitsType; steps: number; } -let CircleFeatureId = 0; export default class DrawCircle extends DrawFeature { - private startPoint: ILngLat; - private endPoint: ILngLat; + protected startPoint: ILngLat; + protected endPoint: ILngLat; + protected pointFeatures: Feature[]; constructor(scene: Scene, options: Partial = {}) { super(scene, options); - this.selectLayer = new selectRender(this); + this.type = 'circle'; } + + public drawFinish() { + return null; + } + + public setCurrentFeature(feature: Feature) { + this.currentFeature = feature as Feature; + // @ts-ignore + // @ts-ignore + this.pointFeatures = feature.properties.pointFeatures; + // @ts-ignore + this.startPoint = feature.properties.startPoint; + // @ts-ignore + this.endPoint = feature.properties.endPoint; + this.source.setFeatureActive(feature); + } + protected onDragStart = (e: IInteractionTarget) => { this.startPoint = e.lngLat; this.setCursor('grabbing'); this.initCenterLayer(); - this.initDrawFillLayer(); this.centerLayer.setData([this.startPoint]); }; + protected onDragging = (e: IInteractionTarget) => { this.endPoint = e.lngLat; - const feature = this.createFeature(); - this.updateDrawFillLayer(feature); + const feature = this.createFeature() as Feature; + const properties = feature.properties as { pointFeatures: Feature[] }; + this.drawRender.update(featureCollection([feature])); + this.drawVertexLayer.update(featureCollection(properties.pointFeatures)); }; protected onDragEnd = () => { + const feature = this.createFeature(`${this.getUniqId()}`); + const properties = feature.properties as { pointFeatures: Feature[] }; + this.drawRender.update(featureCollection([feature])); + this.drawVertexLayer.update(featureCollection(properties.pointFeatures)); this.emit(DrawEvent.CREATE, this.currentFeature); this.emit(DrawEvent.MODE_CHANGE, DrawModes.SIMPLE_SELECT); this.disable(); }; - protected moveFeature(delta: ILngLat): Feature { - const newFeature = moveFeatures([this.currentFeature as Feature], delta)[0]; - const properties = newFeature.properties as { - startPoint: [number, number]; - endPoint: [number, number]; - }; - const { startPoint, endPoint } = properties; - properties.startPoint = movePoint(startPoint, delta); - properties.endPoint = movePoint(endPoint, delta); - newFeature.properties = properties; + protected moveFeature(delta: ILngLat): void { + const newFeature = moveFeatures([this.currentFeature as Feature], delta); + this.drawRender.updateData(featureCollection(newFeature)); + const newPointFeture = moveFeatures(this.pointFeatures, delta); + this.drawVertexLayer.updateData(featureCollection(newPointFeture)); + const newStartPoint = movePoint( + [this.startPoint.lng, this.startPoint.lat], + delta, + ); this.startPoint = { - lat: startPoint[1], - lng: startPoint[0], + lat: newStartPoint[1], + lng: newStartPoint[0], }; - this.endPoint = { - lat: endPoint[1], - lng: endPoint[0], + newFeature[0].properties = { + ...newFeature[0].properties, + startPoint: this.startPoint, + pointFeatures: newPointFeture, }; - return newFeature; + this.centerLayer.setData([this.startPoint]); + this.setCurrentFeature(newFeature[0]); } - protected createFeature(): FeatureCollection { + protected createFeature(id: string = '0'): Feature { + const points = createPoint([this.endPoint]); const feature = createCircle( [this.startPoint.lng, this.startPoint.lat], [this.endPoint.lng, this.endPoint.lat], { + pointFeatures: points.features, units: this.getOption('units'), steps: this.getOption('steps'), - id: `${CircleFeatureId++}`, + id, }, ); this.setCurrentFeature(feature as Feature); - return { - type: 'FeatureCollection', - features: [feature], - }; + return feature; } - protected editFeature(endPoint: ILngLat): FeatureCollection { + protected editFeature(endPoint: ILngLat): void { this.endPoint = endPoint; - return this.createFeature(); + const newFeature = this.createFeature(); + const properties = newFeature.properties as { pointFeatures: Feature[] }; + this.drawRender.updateData(featureCollection([newFeature])); + this.drawVertexLayer.updateData( + featureCollection(properties.pointFeatures), + ); + } + + protected showOtherLayer() { + this.centerLayer.setData([this.currentFeature?.properties?.startPoint]); + this.centerLayer.show(); + } + + protected hideOtherLayer() { + if (this.currentFeature) { + this.centerLayer.hide(); + } } private initCenterLayer() { - const centerStyle = this.getStyle('active_point'); + const centerStyle = this.getStyle('active').point; const layer = new PointLayer() .source([this.startPoint], { parser: { diff --git a/packages/draw/src/modes/draw_delete.ts b/packages/draw/src/modes/draw_delete.ts new file mode 100644 index 0000000000..136c8d4cda --- /dev/null +++ b/packages/draw/src/modes/draw_delete.ts @@ -0,0 +1,38 @@ +import { IInteractionTarget, ILngLat, Scene } from '@antv/l7'; +import { Feature } from '@turf/helpers'; +import { DrawEvent } from '../util/constant'; +import DrawFeature, { IDrawOption } from './draw_mode'; +export type unitsType = 'degrees' | 'radians' | 'miles' | 'kilometers'; +export interface IDrawCircleOption extends IDrawOption { + units: unitsType; + steps: number; +} + +export default class DrawDelete extends DrawFeature { + private endPoint: ILngLat; + // 绘制完成之后显示 + constructor(scene: Scene, options: Partial = {}) { + super(scene, options); + } + + public enable() { + this.emit(DrawEvent.DELETE, ''); + } + public disable() { + return null; + } + + protected onDragStart(e: any): void { + throw new Error('Method not implemented.'); + } + protected onDragging = (e: IInteractionTarget) => { + return; + }; + + protected onDragEnd = () => { + throw new Error('Method not implemented.'); + }; + protected onClick = () => { + return null; + }; +} diff --git a/packages/draw/src/modes/draw_edit.ts b/packages/draw/src/modes/draw_edit.ts index c61f83ed53..d26e170bf0 100644 --- a/packages/draw/src/modes/draw_edit.ts +++ b/packages/draw/src/modes/draw_edit.ts @@ -1,8 +1,5 @@ -import { IInteractionTarget, ILayer, ILngLat, Popup, Scene } from '@antv/l7'; -import turfCircle from '@turf/circle'; -import turfDistance from '@turf/distance'; -import { Feature, featureCollection, point } from '@turf/helpers'; -import EditRender from '../render/edit'; +import { IInteractionTarget, ILngLat, Scene } from '@antv/l7'; +import { Feature } from '@turf/helpers'; import { DrawEvent } from '../util/constant'; import DrawFeature, { IDrawOption } from './draw_mode'; export type unitsType = 'degrees' | 'radians' | 'miles' | 'kilometers'; @@ -18,7 +15,6 @@ export default class DrawEdit extends DrawFeature { private center: ILngLat; private endPoint: ILngLat; // 绘制完成之后显示 - private editLayer: EditRender; constructor(scene: Scene, options: Partial = {}) { super(scene, options); } @@ -46,6 +42,7 @@ export default class DrawEdit extends DrawFeature { protected onDragEnd = () => { this.emit(DrawEvent.UPDATE, null); + this.resetCursor(); this.disable(); }; protected onClick = () => { diff --git a/packages/draw/src/modes/draw_feature.ts b/packages/draw/src/modes/draw_feature.ts index e8b2550d10..d4d30dd252 100644 --- a/packages/draw/src/modes/draw_feature.ts +++ b/packages/draw/src/modes/draw_feature.ts @@ -1,31 +1,18 @@ -import { - IInteractionTarget, - ILayer, - ILngLat, - IPopup, - LineLayer, - PointLayer, - PolygonLayer, - Popup, - Scene, -} from '@antv/l7'; -import turfCircle from '@turf/circle'; -import turfDistance from '@turf/distance'; +import { IInteractionTarget, ILayer, ILngLat, Popup, Scene } from '@antv/l7'; import { Feature, FeatureCollection, featureCollection, point, } from '@turf/helpers'; -import EditLayer from '../render/edit'; -import RenderLayer from '../render/render'; -import SelectLayer from '../render/selected'; +import DrawRender from '../render/draw'; +import RenderLayer from '../render/draw_result'; +import DrawVertexLayer from '../render/draw_vertex'; import { DrawEvent, DrawModes, unitsType } from '../util/constant'; +import DrawDelete from './draw_delete'; import DrawEdit from './draw_edit'; import DrawMode, { IDrawOption } from './draw_mode'; import DrawSelected from './draw_selected'; -let CircleFeatureId = 0; - export interface IDrawFeatureOption extends IDrawOption { units: unitsType; steps: number; @@ -38,9 +25,11 @@ export default abstract class DrawFeature extends DrawMode { // 绘制完成之后显示 public selectMode: DrawSelected; public editMode: DrawEdit; + public deleteMode: DrawDelete; + protected renderLayer: RenderLayer; - protected selectLayer: SelectLayer; - protected editLayer: EditLayer; + protected drawRender: DrawRender; + protected drawVertexLayer: DrawVertexLayer; protected centerLayer: ILayer; // 编辑过程中显示 @@ -48,20 +37,60 @@ export default abstract class DrawFeature extends DrawMode { protected drawLineLayer: ILayer; constructor(scene: Scene, options: Partial = {}) { super(scene, options); + this.drawRender = new DrawRender(this); + this.drawVertexLayer = new DrawVertexLayer(this); this.renderLayer = new RenderLayer(this); - this.selectLayer = new SelectLayer(this); - this.editLayer = new EditLayer(this); + + // this.editLayer = new EditLayer(this); this.selectMode = new DrawSelected(this.scene, {}); this.editMode = new DrawEdit(this.scene, {}); + this.deleteMode = new DrawDelete(this.scene, {}); + this.selectMode.on(DrawEvent.UPDATE, this.onDrawUpdate); this.selectMode.on(DrawEvent.Move, this.onDrawMove); this.editMode.on(DrawEvent.MODE_CHANGE, this.onModeChange); this.editMode.on(DrawEvent.UPDATE, this.onDrawUpdate); this.editMode.on(DrawEvent.Edit, this.onDrawEdit); this.selectMode.on(DrawEvent.MODE_CHANGE, this.onModeChange); + + this.deleteMode.on(DrawEvent.DELETE, this.onDrawDelete); this.on(DrawEvent.CREATE, this.onDrawCreate); this.on(DrawEvent.MODE_CHANGE, this.onModeChange); } + public abstract drawFinish(): void; + public setCurrentFeature(feature: Feature) { + this.currentFeature = feature as Feature; + // @ts-ignore + // @ts-ignore + this.pointFeatures = feature.properties.pointFeatures; + + this.source.setFeatureActive(feature); + } + public deleteCurrentFeature() { + this.deleteMode.enable(); + } + public disableLayer() { + // this.emit(DrawEvent.MODE_CHANGE, DrawModes.STATIC); + this.drawRender.disableDrag(); + } + public enableLayer() { + this.drawRender.enableDrag(); + } + public clear() { + this.drawRender.hide(); + this.drawVertexLayer.hide(); + this.hideOtherLayer(); + this.emit(DrawEvent.MODE_CHANGE, DrawModes.STATIC); + } + public reset() { + this.drawRender.show(); + this.drawVertexLayer.show(); + this.showOtherLayer(); + } + + public addVertex(feature: Feature): void { + throw new Error('子类未实现该方法'); + } protected getDefaultOptions() { return { steps: 64, @@ -75,72 +104,15 @@ export default abstract class DrawFeature extends DrawMode { protected abstract onDragEnd(e: IInteractionTarget): void; - protected abstract createFeature(e: ILngLat): FeatureCollection; + protected abstract createFeature(e?: any): Feature; - protected abstract moveFeature(e: ILngLat): Feature; + protected abstract moveFeature(e: ILngLat): void; - protected abstract editFeature(e: any): FeatureCollection; + protected abstract editFeature(e: any): void; - protected ondrawLayerClick = () => { - if (this.currentFeature === null) { - return; - } - this.currentFeature = null; - this.renderLayer.updateData(); - this.centerLayer.setData([]); - this.drawLayer.setData(InitFeature); - this.drawLineLayer.setData(InitFeature); - return; - }; - protected initDrawFillLayer() { - const style = this.getStyle('active_fill'); - const linestyle = this.getStyle('active_line'); - this.drawLayer = new PolygonLayer() - .source(InitFeature) - .color(style.color) - .shape('fill') - .style(style.style); - this.drawLineLayer = new PolygonLayer() - .source(InitFeature) - .color(linestyle.color) - .size(linestyle.size) - .shape('line') - .style(linestyle.style); - this.scene.addLayer(this.drawLayer); - this.scene.addLayer(this.drawLineLayer); - } + protected abstract hideOtherLayer(): void; - protected updateDrawFillLayer(currentData: any) { - this.drawLayer.setData(currentData); - this.drawLineLayer.setData(currentData); - } - - private removeDrawLayer() { - this.scene.removeLayer(this.drawLayer); - this.scene.removeLayer(this.drawLineLayer); - this.scene.removeLayer(this.centerLayer); - } - - private createCircleData(center: ILngLat, endPoint: ILngLat) { - const radius = turfDistance( - point([center.lng, center.lat]), - point([endPoint.lng, endPoint.lat]), - this.getOption('units'), - ); - const feature = turfCircle([center.lng, center.lat], radius, { - units: this.getOption('units'), - steps: this.getOption('steps'), - properties: { - id: `${CircleFeatureId++}`, - active: true, - radius, - center, - endPoint, - }, - }); - this.currentFeature = feature as Feature; - return featureCollection([feature]); - } + protected abstract showOtherLayer(): void; private addDrawPopup(lnglat: ILngLat, dis: number) { const popup = new Popup({ @@ -156,34 +128,50 @@ export default abstract class DrawFeature extends DrawMode { private onModeChange = (mode: DrawModes[any]) => { switch (mode) { case DrawModes.DIRECT_SELECT: - this.selectLayer.hide(); + this.editMode.enable(); this.editMode.setEditFeature(this.currentFeature as Feature); - this.editLayer.updateData( + this.drawRender.updateData( featureCollection([this.currentFeature as Feature]), ); - this.editLayer.show(); + this.drawVertexLayer.updateData( + featureCollection(this.currentFeature?.properties?.pointFeatures), + ); + this.drawVertexLayer.show(); + this.drawVertexLayer.enableEdit(); + this.showOtherLayer(); + this.drawStatus = 'DrawEdit'; break; case DrawModes.SIMPLE_SELECT: - this.renderLayer.updateData(); this.selectMode.setSelectedFeature(this.currentFeature as Feature); - this.selectLayer.updateData( + this.selectMode.enable(); + this.drawRender.enableDrag(); + this.drawRender.updateData( featureCollection([this.currentFeature as Feature]), ); - this.selectLayer.show(); + this.drawVertexLayer.updateData( + featureCollection(this.currentFeature?.properties?.pointFeatures), + ); + this.drawVertexLayer.disableEdit(); + this.drawVertexLayer.show(); + this.drawRender.show(); + this.showOtherLayer(); + this.drawStatus = 'DrawSelected'; break; case DrawModes.STATIC: - this.source.setFeatureUnActive(this.currentFeature as Feature); - this.renderLayer.updateData(); + this.source.updateFeature(this.currentFeature as Feature); + this.source.clearFeatureActive(); + this.drawVertexLayer.hide(); + this.drawVertexLayer.disableEdit(); + this.hideOtherLayer(); + this.renderLayer.update(this.source.data); + this.renderLayer.enableDrag(); + this.drawStatus = 'DrawFinish'; break; } }; private onDrawCreate = (feature: Feature) => { this.source.addFeature(feature); - if (this.popup) { - this.popup.remove(); - } - this.removeDrawLayer(); }; private onDrawUpdate = (feature: Feature) => { @@ -191,14 +179,21 @@ export default abstract class DrawFeature extends DrawMode { }; private onDrawMove = (delta: ILngLat) => { - const feature = this.moveFeature(delta); - this.currentFeature = feature; - this.selectLayer.updateData(featureCollection([feature])); + this.moveFeature(delta); }; private onDrawEdit = (endpoint: ILngLat) => { - const feature = this.editFeature(endpoint); - this.currentFeature = feature.features[0]; - this.editLayer.updateData(feature); + this.editFeature(endpoint); + }; + + private onDrawDelete = () => { + if (this.drawStatus === 'DrawSelected') { + this.clear(); + this.source.removeFeature(this.currentFeature as Feature); + this.renderLayer.update(this.source.data); + // this.reset(); + } + + // this.source.removeFeature(this.currentFeature as Feature }; } diff --git a/packages/draw/src/modes/draw_line.ts b/packages/draw/src/modes/draw_line.ts new file mode 100644 index 0000000000..b41d5716ec --- /dev/null +++ b/packages/draw/src/modes/draw_line.ts @@ -0,0 +1,38 @@ +import { ILngLat, Scene } from '@antv/l7'; +import { Feature, featureCollection } from '@turf/helpers'; +import { unitsType } from '../util/constant'; +import { createLine, createPoint } from '../util/create_geometry'; +import moveFeatures from '../util/move_featrues'; +import { IDrawFeatureOption } from './draw_feature'; +import DrawPolygon from './draw_polygon'; +export interface IDrawRectOption extends IDrawFeatureOption { + units: unitsType; + steps: number; +} +export default class DrawLine extends DrawPolygon { + constructor(scene: Scene, options: Partial = {}) { + super(scene, options); + this.type = 'line'; + } + protected moveFeature(delta: ILngLat): Feature { + const newFeature = moveFeatures([this.currentFeature as Feature], delta); + const newPointFeture = moveFeatures(this.pointFeatures, delta); + this.drawRender.updateData(featureCollection(newFeature)); + this.drawVertexLayer.updateData(featureCollection(newPointFeture)); + this.currentFeature = newFeature[0]; + this.pointFeatures = newPointFeture; + return this.currentFeature; + } + protected createFeature(points: ILngLat[]): Feature { + const pointfeatures = createPoint(this.points); + this.pointFeatures = pointfeatures.features; + const feature = createLine(points, { + id: this.getUniqId(), + type: 'line', + active: true, + pointFeatures: this.pointFeatures, + }); + this.setCurrentFeature(feature as Feature); + return feature; + } +} diff --git a/packages/draw/src/modes/draw_mode.ts b/packages/draw/src/modes/draw_mode.ts index ab48d72b5c..930d32d7e9 100644 --- a/packages/draw/src/modes/draw_mode.ts +++ b/packages/draw/src/modes/draw_mode.ts @@ -1,7 +1,7 @@ import { IInteractionTarget, IPopup, Scene } from '@antv/l7'; import { Feature, FeatureCollection } from '@turf/helpers'; import { EventEmitter } from 'eventemitter3'; -import { merge, throttle } from 'lodash'; +import { merge } from 'lodash'; import DrawSource from '../source'; import LayerStyles from '../util/layerstyle'; @@ -17,9 +17,14 @@ export type DrawStatus = | 'DrawFinish' | 'EditFinish'; +let DrawFeatureId = 0; + export default abstract class DrawMode extends EventEmitter { public source: DrawSource; public scene: Scene; + public type: string; + public isEnable: boolean = false; + protected options: { [key: string]: any; } = { @@ -27,7 +32,7 @@ export default abstract class DrawMode extends EventEmitter { }; protected drawStatus: DrawStatus = 'Drawing'; protected currentFeature: Feature | null; - protected isEnable: boolean = false; + protected currentVertex: Feature | null; protected popup: IPopup; constructor(scene: Scene, options: Partial = {}) { super(); @@ -45,6 +50,7 @@ export default abstract class DrawMode extends EventEmitter { this.scene.on('dragstart', this.onDragStart); this.scene.on('dragging', this.onDragging); this.scene.on('dragend', this.onDragEnd); + this.scene.on('click', this.onClick); this.setCursor(this.getOption('cursor')); this.isEnable = true; } @@ -56,7 +62,7 @@ export default abstract class DrawMode extends EventEmitter { this.scene.off('dragstart', this.onDragStart); this.scene.off('dragging', this.onDragging); this.scene.off('dragend', this.onDragEnd); - // this.scene.off('click', this.onClick); + this.scene.off('click', this.onClick); this.resetCursor(); // @ts-ignore this.scene.map.dragPan.enable(); @@ -67,23 +73,42 @@ export default abstract class DrawMode extends EventEmitter { this.source.setFeatureActive(feature); } + public setCurrentVertex(feature: Feature) { + this.currentVertex = feature; + } + public deleteCurrentFeature() { + throw new Error('子类未实现该方法'); + } + + public getCurrentVertex(feature: Feature) { + return this.currentVertex; + } + public getCurrentFeature() { + return this.currentVertex; + } + public getOption(key: string) { return this.options[key]; } + public getStyle(id: string) { return this.getOption('style')[id]; } + public getUniqId() { + return DrawFeatureId++; + } + public setCursor(cursor: string) { - const container = this.scene.getContainer(); + const container = this.scene.getMapCanvasContainer(); if (container) { container.style.cursor = cursor; } } public resetCursor() { - const container = this.scene.getContainer(); + const container = this.scene.getMapCanvasContainer(); if (container) { - container.style.cursor = 'default'; + container.removeAttribute('style'); } } @@ -96,4 +121,8 @@ export default abstract class DrawMode extends EventEmitter { protected abstract onDragging(e: IInteractionTarget): void; protected abstract onDragEnd(e: IInteractionTarget): void; + + protected onClick(e: IInteractionTarget): any { + return null; + } } diff --git a/packages/draw/src/modes/draw_point.ts b/packages/draw/src/modes/draw_point.ts index e69de29bb2..fc0a315937 100644 --- a/packages/draw/src/modes/draw_point.ts +++ b/packages/draw/src/modes/draw_point.ts @@ -0,0 +1,80 @@ +import { IInteractionTarget, ILayer, ILngLat, Scene } from '@antv/l7'; +import { + Feature, + FeatureCollection, + featureCollection, + point, +} from '@turf/helpers'; +import { DrawEvent, DrawModes, unitsType } from '../util/constant'; +import moveFeatures from '../util/move_featrues'; +import DrawFeature, { IDrawFeatureOption } from './draw_feature'; +export interface IDrawRectOption extends IDrawFeatureOption { + units: unitsType; + steps: number; +} +export default class DrawPoint extends DrawFeature { + protected pointFeatures: Feature[]; + + constructor(scene: Scene, options: Partial = {}) { + super(scene, options); + this.type = 'point'; + } + + public drawFinish() { + this.emit(DrawEvent.CREATE, this.currentFeature); + this.emit(DrawEvent.MODE_CHANGE, DrawModes.SIMPLE_SELECT); + this.disable(); + } + + protected onDragStart = (e: IInteractionTarget) => { + return null; + }; + protected onDragging = (e: IInteractionTarget) => { + return null; + }; + + protected onDragEnd = () => { + return null; + }; + + protected onClick = (e: any) => { + const lngLat = e.lngLat; + const feature = this.createFeature(lngLat); + this.drawRender.update(featureCollection([feature])); + this.drawVertexLayer.update(featureCollection([feature])); + this.drawFinish(); + }; + + protected moveFeature(delta: ILngLat): Feature { + const newFeature = moveFeatures([this.currentFeature as Feature], delta); + this.drawRender.updateData(featureCollection(newFeature)); + this.drawVertexLayer.updateData(featureCollection(newFeature)); + this.currentFeature = newFeature[0]; + this.pointFeatures = newFeature; + this.currentFeature.properties = { + ...this.currentFeature.properties, + pointFeatures: newFeature, + }; + return this.currentFeature; + } + protected createFeature(p: ILngLat): Feature { + const feature = point([p.lng, p.lat], { + id: this.getUniqId(), + pointFeatures: [point([p.lng, p.lat])], + }); + this.setCurrentFeature(feature as Feature); + return feature; + } + + protected editFeature(endPoint: ILngLat): void { + this.createFeature(endPoint); + } + + protected showOtherLayer() { + return null; + } + + protected hideOtherLayer() { + return null; + } +} diff --git a/packages/draw/src/modes/draw_polygon.ts b/packages/draw/src/modes/draw_polygon.ts new file mode 100644 index 0000000000..049689a50b --- /dev/null +++ b/packages/draw/src/modes/draw_polygon.ts @@ -0,0 +1,250 @@ +import { IInteractionTarget, ILayer, ILngLat, Scene } from '@antv/l7'; +import { + Feature, + FeatureCollection, + featureCollection, + Geometries, + point, + Position, + Properties, +} from '@turf/helpers'; +import DrawMidVertex from '../render/draw_mid_vertex'; +import { DrawEvent, DrawModes, unitsType } from '../util/constant'; +import { createPoint, createPolygon } from '../util/create_geometry'; +import moveFeatures from '../util/move_featrues'; +import DrawFeature, { IDrawFeatureOption } from './draw_feature'; +export interface IDrawRectOption extends IDrawFeatureOption { + units: unitsType; + steps: number; +} +export default class DrawPolygon extends DrawFeature { + protected startPoint: ILngLat; + protected endPoint: ILngLat; + protected points: ILngLat[] = []; + protected pointFeatures: Feature[]; + protected drawMidVertexLayer: DrawMidVertex; + + constructor(scene: Scene, options: Partial = {}) { + super(scene, options); + this.type = 'polygon'; + this.drawMidVertexLayer = new DrawMidVertex(this); + this.on(DrawEvent.MODE_CHANGE, this.addMidLayerEvent); + } + public enable() { + super.enable(); + this.scene.on('mousemove', this.onMouseMove); + this.scene.on('dblclick', this.onDblClick); + // 关闭双击放大 + } + + public disable() { + super.disable(); + this.scene.off('mousemove', this.onMouseMove); + this.scene.off('dblclick', this.onDblClick); + } + + public drawFinish() { + const feature = this.createFeature(this.points); + const properties = feature.properties as { pointFeatures: Feature[] }; + this.drawRender.update(featureCollection([feature])); + this.drawVertexLayer.update(featureCollection(properties.pointFeatures)); + // @ts-ignore + // feature.properties.pointFeatures = pointfeatures; + this.emit(DrawEvent.CREATE, this.currentFeature); + this.emit(DrawEvent.MODE_CHANGE, DrawModes.SIMPLE_SELECT); + this.points = []; + this.disable(); + } + + public addVertex(vertex: Feature) { + // @ts-ignore + const id = vertex.properties.id; + const coord = vertex.geometry.coordinates as Position; + const feature = this.currentFeature as Feature; + const type = feature.geometry.type; + const points = []; + if (type === 'Polygon') { + const coords = feature.geometry.coordinates as Position[][]; + coords[0].splice(id + 1, 0, coord); + for (let i = 0; i < coords[0].length - 1; i++) { + points.push({ + lng: coords[0][i][0], + lat: coords[0][i][1], + }); + } + } else { + const coords = feature.geometry.coordinates as Position[]; + coords.splice(id + 1, 0, coord); + for (const coor of coords) { + points.push({ + lng: coor[0], + lat: coor[1], + }); + } + } + const pointfeatures = createPoint(points); + this.pointFeatures = pointfeatures.features; + this.drawRender.updateData(featureCollection([feature])); + this.drawVertexLayer.updateData(pointfeatures); + this.drawMidVertexLayer.updateData(featureCollection(this.pointFeatures)); + // @ts-ignore + feature.properties.pointFeatures = pointfeatures.features; + this.setCurrentFeature(feature); + } + protected onDragStart = (e: IInteractionTarget) => { + return null; + }; + protected onDragging = (e: IInteractionTarget) => { + return null; + }; + + protected onDragEnd = () => { + return null; + }; + + protected onClick = (e: any) => { + const lngLat = e.lngLat; + this.endPoint = lngLat; + this.points.push(lngLat); + const feature = this.createFeature(this.points); + const properties = feature.properties as { pointFeatures: Feature[] }; + // const pointfeatures = createPoint([this.points[0], this.endPoint]); + // this.pointFeatures = pointfeatures.features; + this.drawRender.update(featureCollection([feature])); + this.drawVertexLayer.update(featureCollection(properties.pointFeatures)); + this.onDraw(); + }; + + protected onMouseMove = (e: any) => { + const lngLat = e.lngLat; + if (this.points.length === 0) { + return; + } + const tmpPoints = this.points.slice(); + tmpPoints.push(lngLat); + const feature = this.createFeature(tmpPoints); + this.drawRender.update(featureCollection([feature])); + }; + + protected onDblClick = (e: any) => { + const lngLat = e.lngLat; + if (this.points.length < 2) { + return; + } + this.points.push(lngLat); + this.drawFinish(); + }; + + protected moveFeature(delta: ILngLat): void { + const newFeature = moveFeatures([this.currentFeature as Feature], delta); + const newPointFeture = moveFeatures(this.pointFeatures, delta); + this.drawRender.updateData(featureCollection(newFeature)); + this.drawVertexLayer.updateData(featureCollection(newPointFeture)); + newFeature[0].properties = { + ...newFeature[0].properties, + pointFeatures: newPointFeture, + }; + this.setCurrentFeature(newFeature[0]); + } + protected createFeature(points: ILngLat[]): Feature { + const pointfeatures = createPoint(this.points); + this.pointFeatures = pointfeatures.features; + const feature = createPolygon(points, { + id: this.getUniqId(), + type: 'polygon', + active: true, + pointFeatures: this.pointFeatures, + }); + this.setCurrentFeature(feature as Feature); + return feature; + } + + protected editFeature(vertex: ILngLat) { + const selectVertexed = this.currentVertex as Feature< + Geometries, + Properties + >; + if (selectVertexed === null) { + return featureCollection([]); + } else { + // @ts-ignore + const id = selectVertexed.properties.id * 1; + selectVertexed.geometry.coordinates = [vertex.lng, vertex.lat]; + // @ts-ignore + this.pointFeatures[id].geometry.coordinates = [vertex.lng, vertex.lat]; + this.drawVertexLayer.updateData(featureCollection(this.pointFeatures)); + this.drawMidVertexLayer.updateData(featureCollection(this.pointFeatures)); + this.editPolygonVertex(id, vertex); + this.drawRender.updateData( + featureCollection([this.currentFeature as Feature]), + ); + const feature = this.currentFeature as Feature; + feature.properties = { + ...this.currentFeature?.properties, + pointFeatures: this.pointFeatures, + }; + this.setCurrentFeature(feature); + } + } + + protected onDraw = () => { + this.drawVertexLayer.on('mousemove', (e: any) => { + this.setCursor('pointer'); + }); + this.drawVertexLayer.on('mouseout', () => { + this.setCursor('crosshair'); + }); + this.drawVertexLayer.on('click', () => { + this.resetCursor(); + this.drawFinish(); + }); + }; + + protected showOtherLayer() { + // if (this.editMode.isEnable) { + // this.drawMidVertexLayer.update(featureCollection(this.pointFeatures)); + // this.drawMidVertexLayer.show(); + // } + return null; + } + + protected hideOtherLayer() { + return null; + } + + protected addMidLayerEvent(mode: DrawModes[any]) { + switch (mode) { + case DrawModes.DIRECT_SELECT: + this.drawMidVertexLayer.update(featureCollection(this.pointFeatures)); + this.drawMidVertexLayer.show(); + break; + case DrawModes.STATIC: + this.drawMidVertexLayer.hide(); + break; + } + } + + private editPolygonVertex(id: number, vertex: ILngLat) { + const feature = this.currentFeature as Feature; + const type = feature.geometry.type; + if (type === 'Polygon') { + const coords = feature.geometry.coordinates as Position[][]; + coords[0][id] = [vertex.lng, vertex.lat]; + if (-id === 0) { + coords[0][coords[0].length - 1] = [vertex.lng, vertex.lat]; + } + } else { + const coords = feature.geometry.coordinates as Position[]; + coords[id] = [vertex.lng, vertex.lat]; + } + this.setCurrentFeature(feature); + this.drawRender.updateData( + featureCollection([this.currentFeature as Feature]), + ); + } +} +/** + * draw 端点响应事件 + * select Polyon 响应事件 + * edit 端点 中心点响应事件 + */ diff --git a/packages/draw/src/modes/draw_rect.ts b/packages/draw/src/modes/draw_rect.ts index 1f3e8ce58c..741f942627 100644 --- a/packages/draw/src/modes/draw_rect.ts +++ b/packages/draw/src/modes/draw_rect.ts @@ -1,103 +1,38 @@ +import { Scene } from '@antv/l7'; import { - IInteractionTarget, - ILayer, - ILngLat, - IPopup, - LineLayer, - PointLayer, - PolygonLayer, - Popup, - Scene, -} from '@antv/l7'; -import { Feature, FeatureCollection, point } from '@turf/helpers'; -import selectRender from '../render/selected'; -import { DrawEvent, DrawModes, unitsType } from '../util/constant'; -import { creatRect } from '../util/create_geometry'; -import moveFeatures, { movePoint, moveRing } from '../util/move_featrues'; -import DrawFeature, { IDrawFeatureOption } from './draw_feature'; + Feature, + FeatureCollection, + featureCollection, + point, +} from '@turf/helpers'; +import { unitsType } from '../util/constant'; +import { createPoint, createRect } from '../util/create_geometry'; +import DrawCircle from './draw_circle'; +import { IDrawFeatureOption } from './draw_feature'; export interface IDrawRectOption extends IDrawFeatureOption { units: unitsType; steps: number; } -export default class DrawRect extends DrawFeature { - private startPoint: ILngLat; - private endPoint: ILngLat; +export default class DrawRect extends DrawCircle { constructor(scene: Scene, options: Partial = {}) { super(scene, options); - this.selectLayer = new selectRender(this); + this.type = 'rect'; } - protected onDragStart = (e: IInteractionTarget) => { - this.startPoint = e.lngLat; - this.setCursor('grabbing'); - this.initCenterLayer(); - this.initDrawFillLayer(); - this.centerLayer.setData([this.startPoint]); - }; - protected onDragging = (e: IInteractionTarget) => { - this.endPoint = e.lngLat; - const feature = this.createFeature(); - this.updateDrawFillLayer(feature); - }; - - protected onDragEnd = () => { - this.emit(DrawEvent.CREATE, this.currentFeature); - this.emit(DrawEvent.MODE_CHANGE, DrawModes.SIMPLE_SELECT); - this.disable(); - }; - - protected moveFeature(delta: ILngLat): Feature { - const newFeature = moveFeatures([this.currentFeature as Feature], delta)[0]; - const properties = newFeature.properties as { - startPoint: [number, number]; - endPoint: [number, number]; - }; - const { startPoint, endPoint } = properties; - properties.startPoint = movePoint(startPoint, delta); - properties.endPoint = movePoint(endPoint, delta); - newFeature.properties = properties; - this.startPoint = { - lat: startPoint[1], - lng: startPoint[0], - }; - this.endPoint = { - lat: endPoint[1], - lng: endPoint[0], - }; - return newFeature; + public drawFinish() { + return null; } - protected createFeature(): FeatureCollection { - const feature = creatRect( + protected createFeature(id: string = '0'): Feature { + const points = createPoint([this.endPoint]); + const feature = createRect( [this.startPoint.lng, this.startPoint.lat], [this.endPoint.lng, this.endPoint.lat], + { + id, + pointFeatures: points.features, + }, ); this.setCurrentFeature(feature as Feature); - return { - type: 'FeatureCollection', - features: [feature], - }; - } - - protected editFeature(endPoint: ILngLat): FeatureCollection { - this.endPoint = endPoint; - return this.createFeature(); - } - - private initCenterLayer() { - const centerStyle = this.getStyle('active_point'); - const layer = new PointLayer() - .source([this.startPoint], { - parser: { - type: 'json', - x: 'lng', - y: 'lat', - }, - }) - .shape('circle') - .color(centerStyle.color) - .size(centerStyle.size) - .style(centerStyle.style); - this.scene.addLayer(layer); - this.centerLayer = layer; + return feature; } } diff --git a/packages/draw/src/modes/draw_selected.ts b/packages/draw/src/modes/draw_selected.ts index 5fd2ca3d5d..31b81739df 100644 --- a/packages/draw/src/modes/draw_selected.ts +++ b/packages/draw/src/modes/draw_selected.ts @@ -9,10 +9,7 @@ import { Popup, Scene, } from '@antv/l7'; -import turfCircle from '@turf/circle'; -import turfDistance from '@turf/distance'; import { Feature, featureCollection, point } from '@turf/helpers'; -import EditRender from '../render/selected'; import { DrawEvent, DrawModes } from '../util/constant'; import moveFeatures from '../util/move_featrues'; import DrawFeature, { IDrawOption } from './draw_mode'; @@ -29,7 +26,6 @@ export default class DrawSelect extends DrawFeature { private center: ILngLat; private dragStartPoint: ILngLat; // 绘制完成之后显示 - private editLayer: EditRender; constructor(scene: Scene, options: Partial = {}) { super(scene, options); // this.editLayer = new EditRender(this); @@ -37,11 +33,6 @@ export default class DrawSelect extends DrawFeature { public setSelectedFeature(feature: Feature) { this.currentFeature = feature; - // this.editLayer.updateData({ - // type: 'FeatureCollection', - // features: [feature], - // }); - // this.editLayer.show(); } protected onDragStart = (e: IInteractionTarget) => { @@ -74,42 +65,4 @@ export default class DrawSelect extends DrawFeature { protected onClick = () => { return null; }; - - private createCircleData(center: ILngLat, endPoint: ILngLat) { - const radius = turfDistance( - point([center.lng, center.lat]), - point([endPoint.lng, endPoint.lat]), - this.getOption('units'), - ); - const feature = turfCircle([center.lng, center.lat], radius, { - units: this.getOption('units'), - steps: this.getOption('steps'), - properties: { - id: `${this.currentFeature?.properties?.id}`, - active: true, - radius, - center, - endPoint, - }, - }); - this.currentFeature = feature as Feature; - return featureCollection([feature]); - } - - private moveCircle(feature: Feature, delta: ILngLat) { - const preCenter = feature?.properties?.center as ILngLat; - const preEndPoint = feature?.properties?.endPoint as ILngLat; - const newCenter = { - lng: preCenter.lng + delta.lng, - lat: preCenter.lat + delta.lat, - }; - const newEndPoint = { - lng: preEndPoint.lng + delta.lng, - lat: preEndPoint.lat + delta.lat, - }; - - const newCircle = this.createCircleData(newCenter, newEndPoint); - // this.centerLayer.setData([newCenter]); - this.editLayer.updateData(newCircle); - } } diff --git a/packages/draw/src/modes/index.ts b/packages/draw/src/modes/index.ts index 3c23d7a710..42b9259d11 100644 --- a/packages/draw/src/modes/index.ts +++ b/packages/draw/src/modes/index.ts @@ -1,3 +1,18 @@ import DrawCircle from './draw_circle'; +import DrawDelete from './draw_delete'; +import DrawFeature from './draw_feature'; +import DrawLine from './draw_line'; +import DrawMode from './draw_mode'; +import DrawPoint from './draw_point'; +import DrawPolygon from './draw_polygon'; import DrawRect from './draw_rect'; -export { DrawCircle, DrawRect }; +export { + DrawCircle, + DrawFeature, + DrawRect, + DrawPolygon, + DrawPoint, + DrawLine, + DrawMode, + DrawDelete, +}; diff --git a/packages/draw/src/render.ts b/packages/draw/src/render.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/draw/src/render/base_render.ts b/packages/draw/src/render/base_render.ts new file mode 100644 index 0000000000..0e5cc1a365 --- /dev/null +++ b/packages/draw/src/render/base_render.ts @@ -0,0 +1,60 @@ +import { IInteractionTarget, ILayer, Scene } from '@antv/l7'; +const InitFeature = { + type: 'FeatureCollection', + features: [], +}; +type CallBack = (...args: any[]) => any; +import { FeatureCollection } from '@turf/helpers'; +import Draw from '../modes/draw_feature'; +import { DrawEvent, DrawModes } from '../util/constant'; +import { renderFeature } from './renderFeature'; +export default class BaseRenderLayer { + public drawLayers: ILayer[] = []; + protected draw: Draw; + protected isEnableDrag: boolean; + protected isEnableEdit: boolean; + constructor(draw: Draw) { + this.draw = draw; + } + public update(feature: FeatureCollection) { + if (this.drawLayers.length > 0) { + this.updateData(feature); + } + this.removeLayers(); + const style = this.draw.getStyle('normal'); + this.drawLayers = renderFeature(feature, style); + this.addLayers(); + } + public on(type: any, handler: CallBack) { + const layer = this.drawLayers[0]; + layer.on(type, handler); + } + public off(type: any, handler: CallBack) { + const layer = this.drawLayers[0]; + layer.off(type, handler); + } + public updateData(data: any) { + this.drawLayers.forEach((layer) => layer.setData(data)); + } + + public destroy() { + this.removeLayers(); + } + + public removeLayers() { + if (this.drawLayers.length !== 0) { + this.drawLayers.forEach((layer) => this.draw.scene.removeLayer(layer)); + } + } + public addLayers() { + this.drawLayers.forEach((layer) => this.draw.scene.addLayer(layer)); + } + + public show() { + this.drawLayers.forEach((layer) => layer.show()); + } + + public hide() { + this.drawLayers.forEach((layer) => layer.hide()); + } +} diff --git a/packages/draw/src/render/draw.ts b/packages/draw/src/render/draw.ts index 80163040c9..dec3a10edc 100644 --- a/packages/draw/src/render/draw.ts +++ b/packages/draw/src/render/draw.ts @@ -1,82 +1,89 @@ -import { - IInteractionTarget, - ILayer, - ILngLat, - IPopup, - LineLayer, - PointLayer, - PolygonLayer, - Popup, - Scene, -} from '@antv/l7'; +import { IInteractionTarget, ILayer, Scene } from '@antv/l7'; const InitFeature = { type: 'FeatureCollection', features: [], }; -import Draw from '../modes/draw_mode'; +import { Feature, FeatureCollection } from '@turf/helpers'; +import Draw from '../modes/draw_feature'; import { DrawEvent, DrawModes } from '../util/constant'; -export default class DrawLayer { - private polygonLayer: ILayer; - private lineLayer: ILayer; - private draw: Draw; - constructor(draw: Draw) { - this.draw = draw; - this.init(); +import BaseRender from './base_render'; +import { renderFeature } from './renderFeature'; +export default class DrawLayer extends BaseRender { + public update(feature: FeatureCollection) { + this.removeLayers(); + const style = this.draw.getStyle('active'); + this.drawLayers = renderFeature(feature, style); + this.addLayers(); } - public init() { - const style = this.draw.getStyle('normal_fill'); - const linestyle = this.draw.getStyle('normal_line'); - this.polygonLayer = new PolygonLayer({ - zIndex: 0, - }) - .source(InitFeature) - .filter('active', (active) => { - return !active; - }) - .shape('fill') - .active(true) - .color(style.color) - .style(style.style); - - this.lineLayer = new LineLayer({ - zIndex: 1, - }) - .source(InitFeature) - .shape('line') - .filter('active', (active) => { - return !active; - }) - .size(linestyle.size) - .color(linestyle.color) - .style(linestyle.style); - this.draw.scene.addLayer(this.polygonLayer); - this.draw.scene.addLayer(this.lineLayer); - this.addLayerEvent(); + public enableDrag() { + this.show(); + if (this.isEnableDrag) { + return; + } + const layer = this.drawLayers[0]; + layer.on('mouseenter', this.onMouseMove); + layer.on('mouseout', this.onUnMouseMove); + layer.on('click', this.onClick); + layer.on('unmousedown', this.onUnClick); + this.isEnableDrag = true; } - public updateData() { - this.lineLayer.setData(this.draw.source.data); - this.polygonLayer.setData(this.draw.source.data); + public disableDrag() { + if (!this.isEnableDrag) { + return; + } + const layer = this.drawLayers[0]; + layer.off('mouseenter', this.onMouseMove); + layer.off('mouseout', this.onUnMouseMove); + layer.off('click', this.onClick); + layer.off('unmousedown', this.onUnClick); + this.isEnableDrag = false; } - public destroy() { - this.draw.scene.removeLayer(this.lineLayer); - this.draw.scene.removeLayer(this.polygonLayer); + public enableEdit() { + if (this.isEnableEdit) { + return; + } + const layer = this.drawLayers[0]; + layer.on('unclick', this.onUnClick); + this.isEnableDrag = true; } - public show() { - this.lineLayer.show(); - this.polygonLayer.show(); + public disableEdit() { + if (!this.isEnableEdit) { + return; + } + const layer = this.drawLayers[0]; + layer.off('unclick', this.onUnClick); + this.isEnableDrag = false; } - public hide() { - this.lineLayer.hide(); - this.polygonLayer.hide(); - } + private onMouseMove = (e: any) => { + this.draw.setCursor('move'); + this.draw.selectMode.enable(); + }; + private onUnMouseMove = (e: any) => { + this.draw.resetCursor(); + this.draw.selectMode.disable(); + }; + private onClick = (e: any) => { + this.draw.selectMode.disable(); + this.draw.editMode.enable(); + this.disableDrag(); + this.draw.resetCursor(); + this.enableEdit(); + this.draw.setCurrentFeature(e.feature); + this.draw.emit(DrawEvent.MODE_CHANGE, DrawModes.DIRECT_SELECT); + }; - private addLayerEvent() { - this.polygonLayer.on('click', (e) => { - this.draw.setCurrentFeature(e.feature); - this.draw.emit(DrawEvent.MODE_CHANGE, DrawModes.SIMPLE_SELECT); - }); - } + private onUnClick = (e: any) => { + this.draw.selectMode.disable(); + this.draw.editMode.disable(); + this.draw.source.setFeatureUnActive( + this.draw.getCurrentFeature() as Feature, + ); + this.disableDrag(); + this.disableEdit(); + this.hide(); + this.draw.emit(DrawEvent.MODE_CHANGE, DrawModes.STATIC); + }; } diff --git a/packages/draw/src/render/draw_mid_vertex.ts b/packages/draw/src/render/draw_mid_vertex.ts new file mode 100644 index 0000000000..ca07dcbbc9 --- /dev/null +++ b/packages/draw/src/render/draw_mid_vertex.ts @@ -0,0 +1,74 @@ +import { + feature, + Feature, + featureCollection, + FeatureCollection, + Point, + Properties, +} from '@turf/helpers'; +import midPoint from '@turf/midpoint'; +import BaseRender from './base_render'; +import { renderFeature } from './renderFeature'; +export default class DrawVertexLayer extends BaseRender { + public update(pointFeatures: FeatureCollection) { + this.removeLayers(); + const midFeatures = this.calcMidPointData(pointFeatures); + const style = this.draw.getStyle('mid_point'); + this.drawLayers = renderFeature(midFeatures, style); + this.addLayers(); + this.enableEdit(); + } + public updateData(data: any) { + const midFeatures = this.calcMidPointData(data); + this.drawLayers.forEach((layer) => layer.setData(midFeatures)); + } + public enableEdit() { + const layer = this.drawLayers[0]; + layer.on('mouseenter', this.onMouseEnter); + layer.on('mouseout', this.onMouseOut); + layer.on('click', this.onClick); + } + + public disableEdit() { + const layer = this.drawLayers[0]; + layer.off('mouseenter', this.onMouseEnter); + layer.off('mouseout', this.onMouseOut); + layer.off('click', this.onClick); + } + + private onMouseEnter = (e: any) => { + this.draw.setCursor('pointer'); + // this.draw.editMode.enable(); + }; + private onMouseOut = (e: any) => { + this.draw.resetCursor(); + // this.draw.editMode.disable(); + }; + private onClick = (e: any) => { + this.draw.addVertex(e.feature); + // 添加一个顶点 1.更新顶点 2.更新重点 + }; + + private calcMidPointData(fe: FeatureCollection) { + const midFeatures: Feature[] = []; + fe.features.forEach((item, index) => { + const preFeature = (item as unknown) as Feature; + if (this.draw.type === 'line' && index === fe.features.length - 1) { + return; + } + const nextFeature = + index !== fe.features.length - 1 + ? ((fe.features[index + 1] as unknown) as Feature) + : ((fe.features[0] as unknown) as Feature); + // @ts-ignore + const point = midPoint(preFeature, nextFeature) as Feature< + Point, + Properties + >; + // @ts-ignore + point.properties.id = index; + midFeatures.push(point); + }); + return featureCollection(midFeatures); + } +} diff --git a/packages/draw/src/render/draw_result.ts b/packages/draw/src/render/draw_result.ts new file mode 100644 index 0000000000..50b933c19d --- /dev/null +++ b/packages/draw/src/render/draw_result.ts @@ -0,0 +1,64 @@ +import { Feature, FeatureCollection } from '@turf/helpers'; +import { DrawEvent, DrawModes } from '../util/constant'; +import BaseRender from './base_render'; +import { renderFeature } from './renderFeature'; +export default class DrawResultLayer extends BaseRender { + public update(feature: FeatureCollection) { + if (this.drawLayers.length > 0) { + this.updateData(feature); + return; + } + this.removeLayers(); + const style = this.draw.getStyle('normal'); + this.drawLayers = renderFeature(feature, style); + this.addFilter(); + this.addLayers(); + } + public enableDrag() { + if (this.isEnableDrag) { + return; + } + const layer = this.drawLayers[0]; + layer.on('click', this.onClick); + this.isEnableDrag = true; + } + public disableDrag() { + if (!this.isEnableDrag) { + return; + } + const layer = this.drawLayers[0]; + layer.off('click', this.onClick); + this.isEnableDrag = false; + } + public enableDelete() { + this.disableDrag(); + const layer = this.drawLayers[0]; + layer.on('click', this.onDeleteClick); + } + + public disableDelete() { + const layer = this.drawLayers[0]; + layer.off('click', this.onDeleteClick); + } + public addFilter() { + this.drawLayers.forEach((layer) => + layer.filter('active', (active) => { + return !active; + }), + ); + } + private onClick = (e: any) => { + this.draw.source.setFeatureUnActive( + this.draw.getCurrentFeature() as Feature, + ); + this.draw.setCurrentFeature(e.feature); + this.draw.source.setFeatureActive(e.feature as Feature); + this.updateData(this.draw.source.data); + this.draw.emit(DrawEvent.MODE_CHANGE, DrawModes.SIMPLE_SELECT); + }; + + private onDeleteClick = (e: any) => { + this.draw.source.removeFeature(e.feature); + this.updateData(this.draw.source.data); + }; +} diff --git a/packages/draw/src/render/draw_vertex.ts b/packages/draw/src/render/draw_vertex.ts new file mode 100644 index 0000000000..d936161107 --- /dev/null +++ b/packages/draw/src/render/draw_vertex.ts @@ -0,0 +1,53 @@ +import { FeatureCollection } from '@turf/helpers'; +import BaseRender from './base_render'; +import { renderFeature } from './renderFeature'; +export default class DrawVertexLayer extends BaseRender { + public update(feature: FeatureCollection) { + this.removeLayers(); + const style = this.draw.getStyle('active'); + this.drawLayers = renderFeature(feature, style); + this.addLayers(); + } + public enableDrag() { + return; + } + public disableDrag() { + return; + } + + public enableEdit() { + if (this.isEnableEdit) { + return; + } + const layer = this.drawLayers[0]; + layer.on('mouseenter', this.onMouseEnter); + layer.on('mouseout', this.onMouseOut); + layer.on('click', this.onClick); + this.isEnableEdit = true; + } + + public disableEdit() { + if (!this.isEnableEdit) { + return; + } + const layer = this.drawLayers[0]; + layer.off('mouseenter', this.onMouseEnter); + layer.off('mouseout', this.onMouseOut); + layer.off('click', this.onClick); + this.isEnableEdit = false; + } + + private onMouseEnter = (e: any) => { + this.draw.setCursor('move'); + this.draw.setCurrentVertex(e.feature); + this.draw.editMode.enable(); + }; + private onMouseOut = (e: any) => { + this.draw.resetCursor(); + this.draw.editMode.disable(); + }; + private onClick = (e: any) => { + this.draw.setCurrentVertex(e.feature); + this.draw.editMode.enable(); + }; +} diff --git a/packages/draw/src/render/edit.ts b/packages/draw/src/render/edit.ts deleted file mode 100644 index dfb8d8ae8b..0000000000 --- a/packages/draw/src/render/edit.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { - IInteractionTarget, - ILayer, - ILngLat, - IPopup, - LineLayer, - PointLayer, - PolygonLayer, - Popup, - Scene, -} from '@antv/l7'; -const InitFeature = { - type: 'FeatureCollection', - features: [], -}; -import { Feature } from '@turf/helpers'; -import Draw from '../modes/draw_feature'; -import { DrawEvent, DrawModes } from '../util/constant'; -export default class EditRenderLayer { - private polygonLayer: ILayer; - private lineLayer: ILayer; - private centerLayer: ILayer; - private endPointLayer: ILayer; - private draw: Draw; - private currentFeature: Feature; - constructor(draw: Draw) { - this.draw = draw; - this.init(); - } - public init() { - const style = this.draw.getStyle('active_fill'); - const linestyle = this.draw.getStyle('active_line'); - const centerStyle = this.draw.getStyle('active_point'); - this.polygonLayer = new PolygonLayer() - .source(InitFeature) - .shape('fill') - .color(style.color) - .style(style.style); - - this.lineLayer = new LineLayer() - .source(InitFeature) - .shape('line') - .size(linestyle.size) - .color(linestyle.color) - .style(linestyle.style); - this.centerLayer = new PointLayer({ - zIndex: 3, - blend: 'normal', - }) - .source([], { - parser: { - type: 'json', - x: 'lng', - y: 'lat', - }, - }) - .shape('circle') - .color(centerStyle.color) - .size(centerStyle.size) - .style(centerStyle.style); - this.endPointLayer = new PointLayer({ - zIndex: 4, - blend: 'normal', - }) - .source([], { - parser: { - type: 'json', - x: 'lng', - y: 'lat', - }, - }) - .shape('circle') - .color(centerStyle.color) - .size(centerStyle.size) - .style(centerStyle.style); - - this.draw.scene.addLayer(this.polygonLayer); - this.draw.scene.addLayer(this.lineLayer); - this.draw.scene.addLayer(this.centerLayer); - this.draw.scene.addLayer(this.endPointLayer); - } - public updateData(data: any) { - if (this.currentFeature === undefined) { - this.addLayerEvent(); - } - this.currentFeature = data.features[0]; - this.lineLayer.setData(data); - this.polygonLayer.setData(data); - const properties = data.features[0].properties; - if (properties.startPoint) { - this.centerLayer.setData([ - { - lng: properties.startPoint[0], - lat: properties.startPoint[1], - }, - ]); - } - if (properties.endPoint) { - this.endPointLayer.setData([ - { - lng: properties.endPoint[0], - lat: properties.endPoint[1], - }, - ]); - } - } - - public destroy() { - this.draw.scene.removeLayer(this.lineLayer); - this.draw.scene.removeLayer(this.polygonLayer); - this.draw.scene.removeLayer(this.centerLayer); - this.draw.scene.removeLayer(this.endPointLayer); - } - - public show() { - this.lineLayer.show(); - this.polygonLayer.show(); - this.centerLayer.show(); - this.endPointLayer.show(); - } - - public hide() { - this.lineLayer.hide(); - this.polygonLayer.hide(); - this.centerLayer.hide(); - this.endPointLayer.hide(); - } - - private addLayerEvent() { - this.endPointLayer.on('mousemove', (e) => { - this.draw.setCursor('move'); - this.draw.editMode.enable(); - }); - this.endPointLayer.on('unmousemove', (e) => { - this.draw.resetCursor(); - this.draw.editMode.disable(); - }); - this.polygonLayer.on('unclick', (e) => { - // 取消选中 回到初始态 - this.draw.emit(DrawEvent.MODE_CHANGE, DrawModes.STATIC); - this.draw.editMode.disable(); - this.hide(); - }); - } -} diff --git a/packages/draw/src/render/renderFeature.ts b/packages/draw/src/render/renderFeature.ts new file mode 100644 index 0000000000..6cb0b80717 --- /dev/null +++ b/packages/draw/src/render/renderFeature.ts @@ -0,0 +1,62 @@ +import { ILayer, LineLayer, PointLayer, PolygonLayer } from '@antv/l7'; +import { FeatureCollection } from '@turf/helpers'; +export function renderFeature(fe: FeatureCollection, style: any): ILayer[] { + const type = fe.features[0].geometry.type; + let layers; + switch (type) { + case 'Point': + layers = drawPoint(fe, style.point); + break; + case 'LineString': + layers = drawLine(fe, style.line); + break; + case 'Polygon': + layers = drawPolyon(fe, style.polygon); + break; + } + return layers as ILayer[]; +} + +function drawPoint(fe: FeatureCollection, style: any) { + const layer = new PointLayer() + .source(fe) + .shape('circle') + .color(style.color) + .size(style.size) + .style(style.style); + return [layer]; +} + +function drawLine(fe: FeatureCollection, style: any) { + const layer = new LineLayer({ + pickingBuffer: 3, + }) + .source(fe) + .shape('line') + .color(style.color) + .size(style.size) + .style(style.style); + return [layer]; +} + +function drawPolyon(fe: FeatureCollection, style: any) { + const fill = new PolygonLayer() + .source(fe) + .shape('fill') + .color(style.color) + .size(style.size) + .style({ + opacity: style.style.opacity, + }); + const line = new PolygonLayer() + .source(fe) + .shape('line') + .color(style.style.stroke) + .size(style.style.strokeWidth) + .style({ + opacity: style.style.strokeOpacity, + lineType: style.style.lineType, + dashArray: style.style.dashArray, + }); + return [fill, line]; +} diff --git a/packages/draw/src/render/selected.ts b/packages/draw/src/render/selected.ts deleted file mode 100644 index 08d17e5e07..0000000000 --- a/packages/draw/src/render/selected.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { - IInteractionTarget, - ILayer, - ILngLat, - IPopup, - LineLayer, - PointLayer, - PolygonLayer, - Popup, - Scene, -} from '@antv/l7'; -const InitFeature = { - type: 'FeatureCollection', - features: [], -}; -import { Feature } from '@turf/helpers'; -import Draw from '../modes/draw_feature'; -import { DrawEvent, DrawModes } from '../util/constant'; -export default class EditRenderLayer { - private polygonLayer: ILayer; - private lineLayer: ILayer; - private centerLayer: ILayer; - private endPointLayer: ILayer; - private draw: Draw; - private currentFeature: Feature; - constructor(draw: Draw) { - this.draw = draw; - this.init(); - } - public init() { - const style = this.draw.getStyle('active_fill'); - const linestyle = this.draw.getStyle('active_line'); - const centerStyle = this.draw.getStyle('active_point'); - this.polygonLayer = new PolygonLayer() - .source(InitFeature) - .shape('fill') - .color(style.color) - .style(style.style); - - this.lineLayer = new LineLayer() - .source(InitFeature) - .shape('line') - .size(linestyle.size) - .color(linestyle.color) - .style(linestyle.style); - this.centerLayer = new PointLayer({ - zIndex: 3, - blend: 'normal', - }) - .source([], { - parser: { - type: 'json', - x: 'lng', - y: 'lat', - }, - }) - .shape('circle') - .color(centerStyle.color) - .size(centerStyle.size) - .style(centerStyle.style); - - this.endPointLayer = new PointLayer({ - zIndex: 4, - blend: 'normal', - }) - .source([], { - parser: { - type: 'json', - x: 'lng', - y: 'lat', - }, - }) - .shape('circle') - .color(centerStyle.color) - .size(centerStyle.size) - .style(centerStyle.style); - - this.draw.scene.addLayer(this.polygonLayer); - this.draw.scene.addLayer(this.lineLayer); - this.draw.scene.addLayer(this.centerLayer); - } - - public updateData(data: any) { - if (this.currentFeature === undefined) { - this.addLayerEvent(); - } - this.currentFeature = data.features[0]; - this.lineLayer.setData(data); - this.polygonLayer.setData(data); - const properties = data.features[0].properties; - if (properties.startPoint) { - this.centerLayer.setData([ - { - lng: properties.startPoint[0], - lat: properties.startPoint[1], - }, - ]); - } - if (properties.endPoint) { - this.endPointLayer.setData([ - { - lng: properties.endPoint[0], - lat: properties.endPoint[1], - }, - ]); - } - } - - public destroy() { - this.draw.scene.removeLayer(this.lineLayer); - this.draw.scene.removeLayer(this.polygonLayer); - // this.draw.scene.removeLayer(this.centerLayer); - } - - public show() { - this.lineLayer.show(); - this.polygonLayer.show(); - // this.centerLayer.show(); - } - - public hide() { - this.lineLayer.hide(); - this.polygonLayer.hide(); - // this.centerLayer.hide(); - } - private addLayerEvent() { - this.polygonLayer.on('mousemove', (e) => { - this.draw.setCursor('move'); - this.draw.selectMode.enable(); - }); - this.polygonLayer.on('unmousemove', (e) => { - this.draw.resetCursor(); - this.draw.selectMode.disable(); - }); - - this.polygonLayer.on('click', (e) => { - // 进入编辑态 - this.draw.selectMode.disable(); - this.draw.emit(DrawEvent.MODE_CHANGE, DrawModes.DIRECT_SELECT); - this.hide(); - }); - this.polygonLayer.on('unclick', (e) => { - // 取消选中 回到初始态 - this.draw.emit(DrawEvent.MODE_CHANGE, DrawModes.STATIC); - this.draw.selectMode.disable(); - this.hide(); - }); - } -} diff --git a/packages/draw/src/source.ts b/packages/draw/src/source.ts index 88fe7ca1e9..bcb52f8282 100644 --- a/packages/draw/src/source.ts +++ b/packages/draw/src/source.ts @@ -28,12 +28,20 @@ export default class DrawSource { fe.properties.active = true; } } + public setFeatureUnActive(feature: Feature) { const fe = this.getFeature(feature?.properties?.id); if (fe && fe.properties) { fe.properties.active = false; } } + public clearFeatureActive() { + this.data.features.forEach((fe: Feature) => { + if (fe && fe.properties) { + fe.properties.active = false; + } + }); + } public updateFeature(feature: Feature) { this.removeFeature(feature); this.addFeature(feature); diff --git a/packages/draw/src/util/create_geometry.ts b/packages/draw/src/util/create_geometry.ts index 88323d31b4..fca031f7e2 100644 --- a/packages/draw/src/util/create_geometry.ts +++ b/packages/draw/src/util/create_geometry.ts @@ -1,6 +1,12 @@ import turfCircle from '@turf/circle'; import turfDistance from '@turf/distance'; -import { Feature, featureCollection, point } from '@turf/helpers'; +import { + Feature, + featureCollection, + lineString, + point, + polygon, +} from '@turf/helpers'; import { unitsType } from './constant'; export function createCircle( @@ -10,6 +16,7 @@ export function createCircle( units: unitsType; steps: number; id: string; + pointFeatures: Feature[]; }, ): Feature { const radius = turfDistance(point(center), point(endPoint), options); @@ -17,20 +24,32 @@ export function createCircle( units: options.units, steps: options.steps, properties: { - id: options.id, + ...options, active: true, + type: 'circle', radius, - startPoint: center, - endPoint, + startPoint: { + lng: center[0], + lat: center[1], + }, + endPoint: { + lng: endPoint[0], + lat: endPoint[1], + }, path: [center, endPoint], }, }); return feature as Feature; } -export function creatRect( +export function createRect( startPoint: [number, number], endPoint: [number, number], + options: { + id: string; + pointFeatures: Feature[]; + [key: string]: any; + }, ): Feature { const minX = Math.min(startPoint[0], endPoint[0]); const minY = Math.min(startPoint[1], endPoint[1]); @@ -39,9 +58,17 @@ export function creatRect( const feature = { type: 'Feature', properties: { + type: 'rect', active: true, - startPoint, - endPoint, + startPoint: { + lng: startPoint[0], + lat: startPoint[1], + }, + endPoint: { + lng: endPoint[0], + lat: endPoint[1], + }, + ...options, }, geometry: { type: 'Polygon', @@ -58,3 +85,44 @@ export function creatRect( }; return feature as Feature; } + +export function createPolygon( + points: Array<{ lng: number; lat: number }>, + options: { + id?: string | number; + pointFeatures: Feature[]; + [key: string]: any; + }, +): any { + const coords = points.map((p) => [p.lng, p.lat]); + if (points.length < 2) { + return point(coords[0], options); + } else if (points.length < 3) { + return lineString(coords, options); + } else { + coords.push(coords[0]); + return polygon([coords], options); + } +} + +export function createLine( + points: Array<{ lng: number; lat: number }>, + options: any, +): any { + const coords = points.map((p) => [p.lng, p.lat]); + if (points.length < 2) { + return point(coords[0], options); + } else { + return lineString(coords, options); + } +} + +export function createPoint(points: Array<{ lng: number; lat: number }>) { + const features = points.map((p, index) => + point([p.lng, p.lat], { + active: true, + id: index.toString(), + }), + ); + return featureCollection(features); +} diff --git a/packages/draw/src/util/layerstyle.ts b/packages/draw/src/util/layerstyle.ts index 45248e7aed..0b57f0e540 100644 --- a/packages/draw/src/util/layerstyle.ts +++ b/packages/draw/src/util/layerstyle.ts @@ -1,20 +1,71 @@ const LayerStyles = { - // 正常显示样式 - normal_fill: { - type: 'PolygonLayer', - shape: 'fill', - color: '#3bb2d0', - style: { - opacity: 0.1, + active: { + point: { + type: 'PointLayer', + shape: 'circle', + color: '#fbb03b', + size: 5, + style: { + stroke: '#fff', + strokeWidth: 2, + }, + }, + line: { + type: 'LineLayer', + shape: 'line', + color: '#fbb03b', + size: 1, + style: { + opacity: 1, + lineType: 'dash', + dashArray: [2, 2], + }, + }, + polygon: { + shape: 'fill', + color: '#fbb03b', + style: { + opacity: 0.1, + stroke: '#fbb03b', + strokeWidth: 1, + strokeOpacity: 1, + lineType: 'dash', + dashArray: [2, 2], + }, }, }, - // xai'm'z - active_fill: { - type: 'PolygonLayer', - shape: 'fill', - color: '#fbb03b', - style: { - opacity: 0.1, + normal: { + polygon: { + type: 'PolygonLayer', + shape: 'fill', + color: '#3bb2d0', + style: { + opacity: 0.1, + stroke: '#3bb2d0', + strokeWidth: 1, + strokeOpacity: 1, + lineType: 'solid', + dashArray: [2, 2], + }, + }, + line: { + type: 'LineLayer', + shape: 'line', + size: 1, + color: '#3bb2d0', + style: { + opacity: 1, + }, + }, + point: { + type: 'PointLayer', + shape: 'circle', + color: '#3bb2d0', + size: 3, + style: { + stroke: '#fff', + strokeWidth: 2, + }, }, }, normal_point: { @@ -28,40 +79,12 @@ const LayerStyles = { }, }, mid_point: { - type: 'PointLayer', - shape: 'circle', - color: '#fbb03b', - size: 3, - style: {}, - }, - active_point: { - type: 'PointLayer', - shape: 'circle', - color: '#fbb03b', - size: 5, - style: { - stroke: '#fff', - strokeWidth: 2, - }, - }, - normal_line: { - type: 'LineLayer', - shape: 'line', - size: 1, - color: '#3bb2d0', - style: { - opacity: 1, - }, - }, - active_line: { - type: 'LineLayer', - shape: 'line', - color: '#fbb03b', - size: 1, - style: { - opacity: 1, - lineType: 'dash', - dashArray: [2, 2], + point: { + type: 'PointLayer', + shape: 'circle', + color: '#fbb03b', + size: 3, + style: {}, }, }, }; diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index 7e10fa9614..4495db1fc1 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -66,7 +66,8 @@ export default class BaseLayer extends EventEmitter public maxZoom: number; public inited: boolean = false; public layerModelNeedUpdate: boolean = false; - public pickedFeatureID: number = -1; + public pickedFeatureID: number | null = null; + public selectedFeatureID: number | null = null; public dataState: IDataState = { dataSourceNeedUpdate: false, @@ -612,6 +613,14 @@ export default class BaseLayer extends EventEmitter public getCurrentPickId(): number | null { return this.currentPickId; } + + public setCurrentSelectedId(id: number) { + this.selectedFeatureID = id; + } + + public getCurrentSelectedId(): number | null { + return this.selectedFeatureID; + } public isVisible(): boolean { const zoom = this.mapService.getZoom(); const { @@ -834,18 +843,28 @@ export default class BaseLayer extends EventEmitter return this.layerService.clock.getElapsedTime() - this.animateStartTime; } - public needPick(): boolean { + public needPick(type: string): boolean { const { enableHighlight = true, enableSelect = true, } = this.getLayerConfig(); - const eventNames = this.eventNames().filter((name) => { - return ( - name !== 'inited' && name !== 'add' && name !== 'remove' && 'dataupdate' // 非拾取事件排除 - ); - }); - const flag = eventNames.length > 0 || enableHighlight || enableSelect; - return this.isVisible() && flag; + // 判断layer是否监听事件; + let isPick = + this.eventNames().indexOf(type) !== -1 || + this.eventNames().indexOf('un' + type) !== -1; + if ((type === 'click' || type === 'dblclick') && enableSelect) { + isPick = true; + } + if ( + type === 'mousemove' && + (enableHighlight || + this.eventNames().indexOf('mouseenter') !== -1 || + this.eventNames().indexOf('unmousemove') !== -1 || + this.eventNames().indexOf('mouseout') !== -1) + ) { + isPick = true; + } + return this.isVisible() && isPick; } public buildModels() { diff --git a/packages/layers/src/line/shaders/line_arc2d_vert.glsl b/packages/layers/src/line/shaders/line_arc2d_vert.glsl index e9cfafb0d0..2e269362a5 100644 --- a/packages/layers/src/line/shaders/line_arc2d_vert.glsl +++ b/packages/layers/src/line/shaders/line_arc2d_vert.glsl @@ -49,7 +49,7 @@ vec2 getExtrusionOffset(vec2 line_clipspace, float offset_direction) { vec2 dir_screenspace = normalize(line_clipspace); // rotate by 90 degrees dir_screenspace = vec2(-dir_screenspace.y, dir_screenspace.x); - vec2 offset = dir_screenspace * offset_direction * a_Size / 2.0; + vec2 offset = dir_screenspace * offset_direction * setPickingSize(a_Size) / 2.0; return offset * vec2(1.0, -1.0); } vec2 getNormal(vec2 line_clipspace, float offset_direction) { diff --git a/packages/layers/src/line/shaders/line_arc_3d_vert.glsl b/packages/layers/src/line/shaders/line_arc_3d_vert.glsl index d0ce46b315..46cae822a2 100644 --- a/packages/layers/src/line/shaders/line_arc_3d_vert.glsl +++ b/packages/layers/src/line/shaders/line_arc_3d_vert.glsl @@ -51,7 +51,7 @@ vec2 getExtrusionOffset(vec2 line_clipspace, float offset_direction) { // rotate by 90 degrees dir_screenspace = vec2(-dir_screenspace.y, dir_screenspace.x); - vec2 offset = dir_screenspace * offset_direction * a_Size / 2.0; + vec2 offset = dir_screenspace * offset_direction * setPickingSize(a_Size) / 2.0; return offset; } diff --git a/packages/layers/src/line/shaders/line_arc_great_circle_vert.glsl b/packages/layers/src/line/shaders/line_arc_great_circle_vert.glsl index f16aaf417c..1c9733742d 100644 --- a/packages/layers/src/line/shaders/line_arc_great_circle_vert.glsl +++ b/packages/layers/src/line/shaders/line_arc_great_circle_vert.glsl @@ -49,7 +49,7 @@ vec2 getExtrusionOffset(vec2 line_clipspace, float offset_direction) { vec2 dir_screenspace = normalize(line_clipspace); // rotate by 90 degrees dir_screenspace = vec2(-dir_screenspace.y, dir_screenspace.x); - vec2 offset = dir_screenspace * offset_direction * a_Size / 2.0; + vec2 offset = dir_screenspace * offset_direction * setPickingSize(a_Size)/ 2.0; return offset; } vec2 getNormal(vec2 line_clipspace, float offset_direction) { diff --git a/packages/layers/src/line/shaders/line_bezier_vert.glsl b/packages/layers/src/line/shaders/line_bezier_vert.glsl index 50a8b340ae..4e86ce49c8 100644 --- a/packages/layers/src/line/shaders/line_bezier_vert.glsl +++ b/packages/layers/src/line/shaders/line_bezier_vert.glsl @@ -48,7 +48,7 @@ vec2 getExtrusionOffset(vec2 line_clipspace, float offset_direction) { vec2 dir_screenspace = normalize(line_clipspace); // rotate by 90 degrees dir_screenspace = vec2(-dir_screenspace.y, dir_screenspace.x); - vec2 offset = dir_screenspace * offset_direction * a_Size / 2.0; + vec2 offset = dir_screenspace * offset_direction * setPickingSize(a_Size) / 2.0; return offset * vec2(1.0, -1.0); } vec2 getNormal(vec2 line_clipspace, float offset_direction) { diff --git a/packages/layers/src/line/shaders/line_dash_vert.glsl b/packages/layers/src/line/shaders/line_dash_vert.glsl index e918c3b10e..aa08df2b3c 100644 --- a/packages/layers/src/line/shaders/line_dash_vert.glsl +++ b/packages/layers/src/line/shaders/line_dash_vert.glsl @@ -29,7 +29,7 @@ void main() { v_normal = vec2(reverse_offset_normal(a_Normal) * sign(a_Miter)); v_color = a_Color; - vec3 size = a_Miter * a_Size * reverse_offset_normal(a_Normal); //v_normal * vec3(1., -1., 1.0); + vec3 size = a_Miter * setPickingSize(a_Size) * reverse_offset_normal(a_Normal); //v_normal * vec3(1., -1., 1.0); vec2 offset = project_pixel(size.xy); vec4 project_pos = project_position(vec4(a_Position.xy, 0, 1.0)); gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, 0, 1.0)); diff --git a/packages/layers/src/line/shaders/line_vert.glsl b/packages/layers/src/line/shaders/line_vert.glsl index 276454fc53..c65416fdf9 100644 --- a/packages/layers/src/line/shaders/line_vert.glsl +++ b/packages/layers/src/line/shaders/line_vert.glsl @@ -38,7 +38,7 @@ void main() { } v_normal = vec2(reverse_offset_normal(a_Normal) * sign(a_Miter)); v_color = a_Color; - vec3 size = a_Miter * a_Size.x * reverse_offset_normal(a_Normal); + vec3 size = a_Miter * setPickingSize(a_Size.x) * reverse_offset_normal(a_Normal); vec2 offset = project_pixel(size.xy); v_side = a_Miter * a_Size.x; vec4 project_pos = project_position(vec4(a_Position.xy, 0, 1.0)); diff --git a/packages/layers/src/plugins/ShaderUniformPlugin.ts b/packages/layers/src/plugins/ShaderUniformPlugin.ts index 95250b8cf7..bf894ba7c9 100644 --- a/packages/layers/src/plugins/ShaderUniformPlugin.ts +++ b/packages/layers/src/plugins/ShaderUniformPlugin.ts @@ -55,6 +55,7 @@ export default class ShaderUniformPlugin implements ILayerPlugin { u_ViewportSize: [width, height], u_DevicePixelRatio: window.devicePixelRatio, u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + u_PickingBuffer: layer.getLayerConfig().pickingBuffer || 0, }), ); diff --git a/packages/layers/src/point/shaders/fill_frag.glsl b/packages/layers/src/point/shaders/fill_frag.glsl index fe66619c68..274f6c3df1 100644 --- a/packages/layers/src/point/shaders/fill_frag.glsl +++ b/packages/layers/src/point/shaders/fill_frag.glsl @@ -55,12 +55,6 @@ void main() { } float opacity_t = smoothstep(0.0, antialiased_blur, outer_df); - if(u_stroke_width <0.01 ) { - gl_FragColor = v_color * opacity_t; - gl_FragColor.a = gl_FragColor.a * u_opacity; - gl_FragColor = filterColor(gl_FragColor); - return; - } float color_t = u_stroke_width < 0.01 ? 0.0 : smoothstep( antialiased_blur, 0.0, @@ -73,8 +67,8 @@ void main() { // gl_FragColor = v_color * color_t; // gl_FragColor = mix(vec4(v_color.rgb, v_color.a * u_opacity), strokeColor * u_stroke_opacity, color_t); - gl_FragColor = opacity_t * mix(vec4(v_color.rgb, v_color.a * u_opacity), strokeColor * u_stroke_opacity, color_t); - + gl_FragColor = mix(vec4(v_color.rgb, v_color.a * u_opacity), strokeColor * u_stroke_opacity, color_t); + gl_FragColor.a = gl_FragColor.a * opacity_t; if(u_aimate.x == Animate) { float d = length(v_data.xy); float intensity = clamp(cos(d * PI), 0.0, 1.0) * clamp(cos(2.0 * PI * (d * 2.0 * u_aimate.z - u_aimate.y * u_time)), 0.0, 1.0); diff --git a/packages/layers/src/point/shaders/fill_vert.glsl b/packages/layers/src/point/shaders/fill_vert.glsl index cf0e996ea2..48946d41ec 100644 --- a/packages/layers/src/point/shaders/fill_vert.glsl +++ b/packages/layers/src/point/shaders/fill_vert.glsl @@ -21,17 +21,19 @@ void main() { float shape_type = a_Shape; - // radius(16-bit) - v_radius = a_Size; + float newSize = setPickingSize(a_Size); - vec2 offset = project_pixel(extrude * (a_Size + u_stroke_width)); + // radius(16-bit) + v_radius = newSize; + + vec2 offset = project_pixel(extrude * (newSize + u_stroke_width)); vec4 project_pos = project_position(vec4(a_Position.xy, 0.0, 1.0)); gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, 0.0, 1.0)); // TODO: billboard // anti-alias - float antialiasblur = 1.0 / (a_Size + u_stroke_width); + float antialiasblur = 1.0 / u_DevicePixelRatio / (newSize + u_stroke_width); // construct point coords v_data = vec4(extrude, antialiasblur,shape_type); diff --git a/packages/layers/src/point/shaders/image_frag.glsl b/packages/layers/src/point/shaders/image_frag.glsl index 847f3f22ad..581ac037b9 100644 --- a/packages/layers/src/point/shaders/image_frag.glsl +++ b/packages/layers/src/point/shaders/image_frag.glsl @@ -25,5 +25,6 @@ float r = 1.0 - smoothstep(radius-(radius*0.01), }else { gl_FragColor= step(0.01, textureColor.z) * v_color; } + gl_FragColor.a =gl_FragColor.a * u_opacity; gl_FragColor = filterColor(gl_FragColor); } diff --git a/packages/layers/src/point/shaders/text_frag.glsl b/packages/layers/src/point/shaders/text_frag.glsl index 477744381d..948ef9d011 100644 --- a/packages/layers/src/point/shaders/text_frag.glsl +++ b/packages/layers/src/point/shaders/text_frag.glsl @@ -29,6 +29,7 @@ void main() { highp float gamma_scaled = gamma * v_gamma_scale; highp float alpha = smoothstep(buff - gamma_scaled, buff + gamma_scaled, dist); - gl_FragColor = mix(vec4(v_color.rgb, v_color.a * u_opacity), vec4(u_stroke.rgb, u_stroke.a * u_opacity), smoothstep(0., 0.5, 1. - dist)) * alpha; + gl_FragColor = mix(vec4(v_color.rgb, v_color.a * u_opacity), vec4(u_stroke.rgb, u_stroke.a * u_opacity), smoothstep(0., 0.5, 1. - dist)); + gl_FragColor.a= gl_FragColor.a * alpha; gl_FragColor = filterColor(gl_FragColor); } diff --git a/packages/maps/src/amap/map.ts b/packages/maps/src/amap/map.ts index 0bdfc7ef5d..caf655bcf6 100644 --- a/packages/maps/src/amap/map.ts +++ b/packages/maps/src/amap/map.ts @@ -108,6 +108,10 @@ export default class AMapService return this.map.getContainer(); } + public getMapCanvasContainer(): HTMLElement { + return this.map.getContainer() as HTMLElement; + } + public getSize(): [number, number] { const size = this.map.getSize(); return [size.getWidth(), size.getHeight()]; diff --git a/packages/maps/src/mapbox/logo.css b/packages/maps/src/mapbox/logo.css new file mode 100644 index 0000000000..f7731d89ca --- /dev/null +++ b/packages/maps/src/mapbox/logo.css @@ -0,0 +1,3 @@ +.mapboxgl-ctrl-logo { + display: none !important; +} diff --git a/packages/maps/src/mapbox/map.ts b/packages/maps/src/mapbox/map.ts index 9164f95463..0e5cc36962 100644 --- a/packages/maps/src/mapbox/map.ts +++ b/packages/maps/src/mapbox/map.ts @@ -20,14 +20,18 @@ import { import { DOM } from '@antv/l7-utils'; import { inject, injectable } from 'inversify'; import mapboxgl, { IControl, Map } from 'mapbox-gl'; + +// tslint:disable-next-line:no-submodule-imports +import 'mapbox-gl/dist/mapbox-gl.css'; import { IMapboxInstance } from '../../typings/index'; +import './logo.css'; import Viewport from './Viewport'; const EventMap: { [key: string]: any; } = { mapmove: 'move', camerachange: 'move', - zoomChange: 'zoom', + zoomchange: 'zoom', dragging: 'drag', }; import { MapTheme } from './theme'; @@ -87,6 +91,9 @@ export default class MapboxService public getContainer(): HTMLElement | null { return this.map.getContainer(); } + public getMapCanvasContainer(): HTMLElement { + return this.map.getCanvasContainer(); + } public getSize(): [number, number] { const size = this.map.transform; @@ -138,18 +145,16 @@ export default class MapboxService this.map.setBearing(rotation); } - public zoomIn(): void { - this.map.zoomIn(); + public zoomIn(option?: any, eventData?: any): void { + this.map.zoomIn(option, eventData); + } + public zoomOut(option?: any, eventData?: any): void { + this.map.zoomOut(option, eventData); } - public setPitch(pitch: number) { return this.map.setPitch(pitch); } - public zoomOut(): void { - this.map.zoomOut(); - } - public panTo(p: [number, number]): void { this.map.panTo(p); } @@ -295,7 +300,6 @@ export default class MapboxService if (this.map) { this.map.remove(); this.$mapContainer = null; - this.removeLogoControl(); } } public emit(name: string, ...args: any[]) { @@ -356,20 +360,6 @@ export default class MapboxService } return $wrapper; } - - private removeLogoControl(): void { - // @ts-ignore - const controls = this.map._controls as IControl[]; - const logoCtr = controls.find((ctr: IControl) => { - if (ctr.hasOwnProperty('_updateLogo')) { - return true; - } - }); - if (logoCtr) { - this.map.removeControl(logoCtr); - } - } - private getMapStyle(name: MapStyle) { if (typeof name !== 'string') { return name; diff --git a/packages/scene/src/index.ts b/packages/scene/src/index.ts index 73d97942ab..5936929499 100644 --- a/packages/scene/src/index.ts +++ b/packages/scene/src/index.ts @@ -118,6 +118,9 @@ class Scene public getMapContainer(): HTMLElement | null { return this.mapService.getMapContainer(); } + public getMapCanvasContainer(): HTMLElement { + return this.mapService.getMapCanvasContainer() as HTMLElement; + } public getMapService(): IMapService { return this.mapService; diff --git a/packages/source/src/transform/grid.ts b/packages/source/src/transform/grid.ts index 5c40fed033..6a1e6e13ee 100644 --- a/packages/source/src/transform/grid.ts +++ b/packages/source/src/transform/grid.ts @@ -19,8 +19,8 @@ export function aggregatorToGrid(data: IParserData, option: ITransform) { const { gridHash, gridOffset } = _pointsGridHash(dataArray, size); const layerData = _getGridLayerDataFromGridHash(gridHash, gridOffset, option); return { - yOffset: gridOffset.yOffset / 1.8, - xOffset: gridOffset.xOffset / 1.8, + yOffset: gridOffset.yOffset / 2, + xOffset: gridOffset.xOffset / 2, radius: gridOffset.xOffset, type: 'grid', dataArray: layerData, @@ -31,6 +31,7 @@ function _pointsGridHash(dataArray: any[], size: number) { let latMin = Infinity; let latMax = -Infinity; let pLat; + for (const point of dataArray) { pLat = point.coordinates[1]; if (Number.isFinite(pLat)) { @@ -38,8 +39,8 @@ function _pointsGridHash(dataArray: any[], size: number) { latMax = pLat > latMax ? pLat : latMax; } } - // const centerLat = (latMin + latMax) / 2; - const centerLat = 34.54083; + const centerLat = (latMin + latMax) / 2; + // const centerLat = 34.54083; const gridOffset = _calculateGridLatLonOffset(size, centerLat); if (gridOffset.xOffset <= 0 || gridOffset.yOffset <= 0) { return { gridHash: {}, gridOffset }; @@ -95,8 +96,8 @@ function _getGridLayerDataFromGridHash( Object.assign(item, { _id: i, coordinates: [ - -180 + gridOffset.xOffset * lonIdx, - -90 + gridOffset.yOffset * latIdx, + -180 + gridOffset.xOffset * (lonIdx + 0.5), + -90 + gridOffset.yOffset * (latIdx + 0.5), ], rawData: gridHash[key].points, count: gridHash[key].count, diff --git a/stories/Components/components/Marker.tsx b/stories/Components/components/Marker.tsx index 5343266a94..eef8155889 100644 --- a/stories/Components/components/Marker.tsx +++ b/stories/Components/components/Marker.tsx @@ -24,7 +24,6 @@ export default class MarkerComponent extends React.Component { zoom: 18, }), }); - this.scene = scene; const popup = new Popup({ offsets: [0, 20], @@ -90,6 +89,7 @@ export default class MarkerComponent extends React.Component { // console.log(this.scene.getZoom()); // console.log('选中的点', 1111); // }); + this.scene = scene; }); } diff --git a/stories/Components/components/Scale.tsx b/stories/Components/components/Scale.tsx index 8613d78417..9251c1272e 100644 --- a/stories/Components/components/Scale.tsx +++ b/stories/Components/components/Scale.tsx @@ -46,13 +46,14 @@ export default class ScaleComponent extends React.Component { '#CF1D49', ]) .shape('fill') - // .select(true) + .select(true) .style({ opacity: 1.0, }); scene.addLayer(layer); const pointLayer = new PointLayer({ name: '02', + enablePropagation: true, }) .source(pointsData, { cluster: true, @@ -63,7 +64,7 @@ export default class ScaleComponent extends React.Component { }) .size('point_count', [5, 10, 15, 20, 25]) .animate(false) - .active(true) + .active(false) .color('yellow') .style({ opacity: 0.5, @@ -71,7 +72,14 @@ export default class ScaleComponent extends React.Component { }); scene.addLayer(pointLayer); layer.on('click', (e) => { - layer.setSelect(e.featureId); + console.log(1, e); + // layer.setSelect(e.featureId); + }); + pointLayer.on('click', (e) => { + console.log(2, e); + }); + pointLayer.on('mouseout', (e) => { + console.log(2, e); }); const scaleControl = new Scale(); const layers = { diff --git a/stories/Draw/Components/DrawControl.tsx b/stories/Draw/Components/DrawControl.tsx new file mode 100644 index 0000000000..b1e50eb5c2 --- /dev/null +++ b/stories/Draw/Components/DrawControl.tsx @@ -0,0 +1,57 @@ +import { Scene } from '@antv/l7'; +import { DrawControl } from '@antv/l7-draw'; +import { GaodeMap, Mapbox } from '@antv/l7-maps'; + +import * as React from 'react'; +export default class Circle extends React.Component { + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'mapbox://styles/mapbox/satellite-v9', // hosted style id + center: [-91.874, 42.76], // starting position + zoom: 12, // starting zoom + }), + }); + this.scene = scene; + + scene.on('loaded', () => { + const drawControl = new DrawControl(scene, { + position: 'topright', + layout: 'horizontal', // horizontal vertical + controls: { + point: true, + polygon: true, + line: true, + circle: true, + rect: true, + delete: true, + }, + }); + scene.on('click', () => {}); + scene.addControl(drawControl); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/Draw/Components/DrawLine.tsx b/stories/Draw/Components/DrawLine.tsx new file mode 100644 index 0000000000..f9b3148cc5 --- /dev/null +++ b/stories/Draw/Components/DrawLine.tsx @@ -0,0 +1,45 @@ +import { LineLayer, PointLayer, PolygonLayer, Popup, Scene } from '@antv/l7'; +import { DrawLine } from '@antv/l7-draw'; +import { GaodeMap, Mapbox } from '@antv/l7-maps'; + +import * as React from 'react'; +export default class Circle extends React.Component { + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'light', + center: [113.775374, 28.31067], + zoom: 12, + }), + }); + this.scene = scene; + + scene.on('loaded', () => { + const drawLine = new DrawLine(scene); + drawLine.enable(); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/Draw/Components/DrawPoint.tsx b/stories/Draw/Components/DrawPoint.tsx new file mode 100644 index 0000000000..933c64a127 --- /dev/null +++ b/stories/Draw/Components/DrawPoint.tsx @@ -0,0 +1,45 @@ +import { LineLayer, PointLayer, PolygonLayer, Popup, Scene } from '@antv/l7'; +import { DrawPoint } from '@antv/l7-draw'; +import { GaodeMap, Mapbox } from '@antv/l7-maps'; + +import * as React from 'react'; +export default class Circle extends React.Component { + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'light', + center: [113.775374, 28.31067], + zoom: 12, + }), + }); + this.scene = scene; + + scene.on('loaded', () => { + const drawPoint = new DrawPoint(scene); + drawPoint.enable(); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/Draw/Components/Polygon.tsx b/stories/Draw/Components/Polygon.tsx new file mode 100644 index 0000000000..b7b42d0fd1 --- /dev/null +++ b/stories/Draw/Components/Polygon.tsx @@ -0,0 +1,45 @@ +import { LineLayer, PointLayer, PolygonLayer, Popup, Scene } from '@antv/l7'; +import { DrawPolygon } from '@antv/l7-draw'; +import { GaodeMap, Mapbox } from '@antv/l7-maps'; + +import * as React from 'react'; +export default class Circle extends React.Component { + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'light', + center: [113.775374, 28.31067], + zoom: 12, + }), + }); + this.scene = scene; + + scene.on('loaded', () => { + const drawPolygon = new DrawPolygon(scene); + drawPolygon.enable(); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/Draw/Draw.stories.tsx b/stories/Draw/Draw.stories.tsx index 708c4fd2b0..e8740eba05 100644 --- a/stories/Draw/Draw.stories.tsx +++ b/stories/Draw/Draw.stories.tsx @@ -2,11 +2,19 @@ import { storiesOf } from '@storybook/react'; import * as React from 'react'; import Circle from './Components/Circle'; import DrawCircle from './Components/DrawCircle'; +import DrawControl from './Components/DrawControl'; +import Line from './Components/DrawLine'; +import Point from './Components/DrawPoint'; import DrawPolygon from './Components/DrawPolygon'; import DrawRect from './Components/DrawRect'; +import Polygon from './Components/Polygon'; storiesOf('绘制', module) .add('圆', () => , {}) + .add('矩形', () => , {}) + .add('多边形', () => , {}) + .add('点', () => , {}) + .add('路径', () => , {}) + .add('绘制组件', () => , {}) .add('绘制圆', () => , {}) - .add('四边形', () => , {}) .add('绘制面', () => , {}); diff --git a/stories/Layers/Layers.stories.tsx b/stories/Layers/Layers.stories.tsx index 9053691c5e..8b6f30a465 100644 --- a/stories/Layers/Layers.stories.tsx +++ b/stories/Layers/Layers.stories.tsx @@ -21,6 +21,7 @@ import WorldDemo from './components/polygon_line'; import ImageLayerDemo from './components/RasterImage'; import RasterLayerDemo from './components/RasterLayer'; import TextLayerDemo from './components/Text'; +import GridTest from './components/gridtest'; // @ts-ignore storiesOf('图层', module) @@ -44,4 +45,5 @@ storiesOf('图层', module) .add('网格热力图', () => ) .add('栅格', () => ) .add('图片', () => ) + .add('网格测试', () => ) .add('世界地图', () => ); diff --git a/stories/Layers/components/Point.tsx b/stories/Layers/components/Point.tsx index 3eb895b846..e6ce612329 100644 --- a/stories/Layers/components/Point.tsx +++ b/stories/Layers/components/Point.tsx @@ -18,7 +18,7 @@ export default class Point3D extends React.Component { style: 'light', center: [-121.24357, 37.58264], pitch: 0, - zoom: 6.45, + zoom: 10.45, }), }); scene.on('loaded', () => { @@ -53,6 +53,7 @@ export default class Point3D extends React.Component { .style({ opacity: 1, strokeWidth: 0, + stroke: '#fff', }); scene.addLayer(pointLayer); diff --git a/stories/Layers/components/Text.tsx b/stories/Layers/components/Text.tsx index d6488ce182..c1c22f4680 100644 --- a/stories/Layers/components/Text.tsx +++ b/stories/Layers/components/Text.tsx @@ -23,7 +23,7 @@ export default class TextLayerDemo extends React.Component { const scene = new Scene({ id: 'map', - map: new Mapbox({ + map: new GaodeMap({ center: [120.19382669582967, 30.258134], pitch: 0, style: 'dark', @@ -41,7 +41,7 @@ export default class TextLayerDemo extends React.Component { }) .shape('s', 'text') // .shape('circle') - .size(8) + .size(18) .filter('t', (t) => { return t < 5; }) @@ -54,7 +54,7 @@ export default class TextLayerDemo extends React.Component { // spacing: 2, // 字符间距 // padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近 stroke: '#fff', // 描边颜色 - strokeWidth: 0, // 描边宽度 + strokeWidth: 1, // 描边宽度 // strokeOpacity: 1.0, }); scene.addLayer(pointLayer); diff --git a/stories/Layers/components/dash.tsx b/stories/Layers/components/dash.tsx index 2e8bfeb558..e253694760 100644 --- a/stories/Layers/components/dash.tsx +++ b/stories/Layers/components/dash.tsx @@ -23,7 +23,9 @@ export default class DashLineDemo extends React.Component { zoom: 14, }), }); - const lineLayer = new LineLayer() + const lineLayer = new LineLayer({ + pickingBuffer: 5, + }) .source(await response.json()) .size(1) .shape('line') diff --git a/stories/Layers/components/gridtest.tsx b/stories/Layers/components/gridtest.tsx new file mode 100644 index 0000000000..cf205035e5 --- /dev/null +++ b/stories/Layers/components/gridtest.tsx @@ -0,0 +1,76 @@ +import { HeatmapLayer, Marker, PointLayer, Scene, IPoint } from '@antv/l7'; +import { GaodeMap, Mapbox } from '@antv/l7-maps'; +import * as dat from 'dat.gui'; +import * as React from 'react'; +export default class HexagonLayerDemo extends React.Component { + // @ts-ignore + private scene: Scene; + private gui: dat.GUI; + + public componentWillUnmount() { + this.scene.destroy(); + if (this.gui) { + this.gui.destroy(); + } + } + public async componentDidMount() { + const testPoint: [number, number] = [113.868222, 22.506306]; + + const scene = new Scene({ + id: 'map', + map: new GaodeMap({ + center: testPoint, + pitch: 0, + zoom: 17, + token: '8e2254ff173dbf7ff5029e9c9df20bc3', + }), + }); + + scene.on('loaded', () => { + // 网格热力图 + const testList = [{ lng: testPoint[0], lat: testPoint[1], lev: 1 }]; + const layer = new HeatmapLayer({}) + .source(testList, { + parser: { + type: 'json', + x: 'lng', + y: 'lat', + }, + transforms: [ + { + type: 'grid', + size: 100, + field: 'lev', + method: 'sum', + }, + ], + }) + .shape('circle') + .style({ + coverage: 1, + }) + .color('count', ['#0B0030', '#6BD5A0'].reverse()); + scene.addLayer(layer); + + // marker + // @ts-ignore + const marker = new Marker().setLnglat(testPoint); + scene.addMarker(marker); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/Layers/components/heatmap3d.tsx b/stories/Layers/components/heatmap3d.tsx index b2e6e5784a..bbf6a2cc32 100644 --- a/stories/Layers/components/heatmap3d.tsx +++ b/stories/Layers/components/heatmap3d.tsx @@ -22,7 +22,7 @@ export default class HeatMapLayerDemo extends React.Component { pitch: 58.5, center: [111.8759, 30.6942], rotation: 0.519, - zoom: 3.6116, + zoom: 14, }), }); scene.on('loaded', () => { diff --git a/stories/Layers/components/hexagon.tsx b/stories/Layers/components/hexagon.tsx index 8eb1c17b32..d4487b8381 100644 --- a/stories/Layers/components/hexagon.tsx +++ b/stories/Layers/components/hexagon.tsx @@ -36,14 +36,14 @@ export default class HexagonLayerDemo extends React.Component { .source(data, { transforms: [ { - type: 'hexagon', + type: 'grid', size: 500000, field: 'name', method: 'mode', }, ], }) - .shape('hexagon') // 支持 circle, hexagon,triangle + .shape('square') // 支持 circle, hexagon,triangle .color('mode', [ '#ffffe5', '#fff7bc', diff --git a/stories/Layers/components/polygon_line.tsx b/stories/Layers/components/polygon_line.tsx index 7f0700445b..cc65d0d42c 100644 --- a/stories/Layers/components/polygon_line.tsx +++ b/stories/Layers/components/polygon_line.tsx @@ -34,12 +34,13 @@ export default class World extends React.Component { style: 'blank', center: [110.19382669582967, 30.258134], pitch: 0, - zoom: 0, + zoom: 5, }), }); this.scene = scene; const layer = new PolygonLayer({ name: '01', + autoFit: true, }); layer