From 79d9e633674cd617939715edfe145a441a028286 Mon Sep 17 00:00:00 2001 From: thinkinggis Date: Fri, 20 Mar 2020 11:13:58 +0800 Subject: [PATCH] feat: add drag event --- build/bundle.ts | 8 ++ .../interaction/IInteractionService.ts | 1 + .../interaction/InteractionService.ts | 44 +++--- .../services/interaction/PickingService.ts | 4 +- .../core/src/services/scene/ISceneService.ts | 4 + .../core/src/services/scene/SceneService.ts | 16 ++- packages/draw/README.md | 2 - packages/draw/rollup.config.js | 33 +++++ packages/draw/src/index.ts | 1 + packages/draw/src/modes/draw_feature.ts | 30 +++++ packages/draw/src/source.ts | 41 ++++++ packages/draw/src/util/constant.ts | 0 packages/draw/src/util/theme.ts | 71 ++++++++++ packages/l7/demo/polygon.html | 6 +- packages/renderer/package.json | 2 +- packages/scene/src/index.ts | 6 +- stories/Draw/Components/DrawCircle.tsx | 114 ++++++++++++++++ stories/Draw/Components/DrawRect.tsx | 126 ++++++++++++++++++ stories/Draw/Draw.stories.tsx | 7 +- yarn.lock | 8 +- 20 files changed, 488 insertions(+), 36 deletions(-) create mode 100644 packages/draw/rollup.config.js create mode 100644 packages/draw/src/index.ts create mode 100644 packages/draw/src/modes/draw_feature.ts create mode 100644 packages/draw/src/source.ts create mode 100644 packages/draw/src/util/constant.ts create mode 100644 packages/draw/src/util/theme.ts create mode 100644 stories/Draw/Components/DrawCircle.tsx create mode 100644 stories/Draw/Components/DrawRect.tsx diff --git a/build/bundle.ts b/build/bundle.ts index c63cf8c7f1..b9ee3d8191 100644 --- a/build/bundle.ts +++ b/build/bundle.ts @@ -1,2 +1,10 @@ // @ts-ignore export * from '@antv/l7'; +// import * as L7 from '@antv/l7'; +// export default L7; + +// import { Scene } from '@antv/l7'; +// // export { Scene }; +// export default { +// Scene, +// }; diff --git a/packages/core/src/services/interaction/IInteractionService.ts b/packages/core/src/services/interaction/IInteractionService.ts index 396021e854..98173b740c 100644 --- a/packages/core/src/services/interaction/IInteractionService.ts +++ b/packages/core/src/services/interaction/IInteractionService.ts @@ -4,6 +4,7 @@ export enum InteractionEvent { Click = 'click', Select = 'select', Active = 'active', + Drag = 'drag', } export interface IInteractionTarget { x: number; diff --git a/packages/core/src/services/interaction/InteractionService.ts b/packages/core/src/services/interaction/InteractionService.ts index 897971229d..3416ce94db 100644 --- a/packages/core/src/services/interaction/InteractionService.ts +++ b/packages/core/src/services/interaction/InteractionService.ts @@ -5,6 +5,12 @@ import { TYPES } from '../../types'; import { ILogService } from '../log/ILogService'; import { ILngLat, IMapService } from '../map/IMapService'; import { IInteractionService, InteractionEvent } from './IInteractionService'; +const DragEventMap: { [key: string]: string } = { + panstart: 'dragstart', + panmove: 'dragging', + panend: 'dragend', + pancancel: 'dragcancle', +}; /** * 由于目前 L7 与地图结合的方案为双 canvas 而非共享 WebGL Context,事件监听注册在地图底图上。 * 除此之外,后续如果支持非地图场景,事件监听就需要注册在 L7 canvas 上。 @@ -61,7 +67,7 @@ export default class InteractionService extends EventEmitter // hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL }); // hammertime.get('pinch').set({ enable: true }); hammertime.on('dblclick click', this.onHammer); - // hammertime.on('panstart panmove panend', this.onHammer); + hammertime.on('panstart panmove panend pancancel', this.onDrag); // hammertime.on('press pressup', this.onHammer); // $containter.addEventListener('touchstart', this.onTouch); $containter.addEventListener('mousemove', this.onHover); @@ -81,6 +87,7 @@ export default class InteractionService extends EventEmitter if ($containter) { $containter.removeEventListener('mousemove', this.onHover); 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); $containter.removeEventListener('mousedown', this.onHover); @@ -89,16 +96,26 @@ export default class InteractionService extends EventEmitter $containter.removeEventListener('contextmenu', this.onHover); } } + private onDrag = (target: HammerInput) => { + const interactionTarget = this.interactionEvent(target); + interactionTarget.type = DragEventMap[interactionTarget.type]; + this.emit(InteractionEvent.Drag, interactionTarget); + }; private onHammer = (target: HammerInput) => { + const interactionTarget = this.interactionEvent(target); + this.emit(InteractionEvent.Hover, interactionTarget); + }; + + private interactionEvent(target: HammerInput) { const { type, pointerType } = target; let clientX; let clientY; if (pointerType === 'touch') { - clientY = target.pointers[0].clientY; - clientX = target.pointers[0].clientX; + clientY = Math.floor(target.pointers[0].clientY); + clientX = Math.floor(target.pointers[0].clientX); } else { - clientY = (target.srcEvent as MouseEvent).y; - clientX = (target.srcEvent as MouseEvent).x; + clientY = Math.floor((target.srcEvent as MouseEvent).y); + clientX = Math.floor((target.srcEvent as MouseEvent).x); } const $containter = this.mapService.getMapContainer(); @@ -108,21 +125,8 @@ export default class InteractionService extends EventEmitter clientY -= top; } const lngLat = this.mapService.containerToLngLat([clientX, clientY]); - this.emit(InteractionEvent.Hover, { x: clientX, y: clientY, lngLat, type }); - }; - private onTouch = (target: TouchEvent) => { - if (target.targetTouches.length > 1) { - return; - } - const touch = target.targetTouches[0]; - // @ts-ignore - this.onHover({ - x: touch.clientX, - y: touch.clientY, - type: 'touch', - }); - }; - + return { x: clientX, y: clientY, lngLat, type }; + } private onHover = ({ x, y, type }: MouseEvent) => { const $containter = this.mapService.getMapContainer(); if ($containter) { diff --git a/packages/core/src/services/interaction/PickingService.ts b/packages/core/src/services/interaction/PickingService.ts index c3f44056c3..795dc3d841 100644 --- a/packages/core/src/services/interaction/PickingService.ts +++ b/packages/core/src/services/interaction/PickingService.ts @@ -110,7 +110,7 @@ export default class PickingService implements IPickingService { const { enableHighlight, enableSelect } = layer.getLayerConfig(); const xInDevicePixel = x * window.devicePixelRatio; - const yInDevicePixel = y * window.devicePixelRatio; + const yInDevicePixel = (height - (y + 1)) * window.devicePixelRatio; if ( xInDevicePixel > width || xInDevicePixel < 0 || @@ -123,7 +123,7 @@ export default class PickingService implements IPickingService { pickedColors = readPixels({ x: Math.floor(xInDevicePixel / PICKSCALE), // 视口坐标系原点在左上,而 WebGL 在左下,需要翻转 Y 轴 - y: Math.floor((height - (y + 1) * window.devicePixelRatio) / PICKSCALE), + y: Math.floor(yInDevicePixel / PICKSCALE), width: 1, height: 1, data: new Uint8Array(1 * 1 * 4), diff --git a/packages/core/src/services/scene/ISceneService.ts b/packages/core/src/services/scene/ISceneService.ts index 35d1bb4097..e5397232f9 100644 --- a/packages/core/src/services/scene/ISceneService.ts +++ b/packages/core/src/services/scene/ISceneService.ts @@ -22,4 +22,8 @@ export const SceneEventList: string[] = [ 'maploaded', 'resize', 'destroy', + 'dragstart', + 'dragging', + 'dragend', + 'dragcancel', ]; diff --git a/packages/core/src/services/scene/SceneService.ts b/packages/core/src/services/scene/SceneService.ts index d78c9d72e4..77601a6271 100644 --- a/packages/core/src/services/scene/SceneService.ts +++ b/packages/core/src/services/scene/SceneService.ts @@ -14,7 +14,11 @@ import { IMarkerService } from '../component/IMarkerService'; import { IPopupService } from '../component/IPopupService'; import { IGlobalConfigService, ISceneConfig } from '../config/IConfigService'; import { ICoordinateSystemService } from '../coordinate/ICoordinateSystemService'; -import { IInteractionService } from '../interaction/IInteractionService'; +import { + IInteractionService, + IInteractionTarget, + InteractionEvent, +} from '../interaction/IInteractionService'; import { IPickingService } from '../interaction/IPickingService'; import { ILayer, ILayerService } from '../layer/ILayerService'; import { ILogService } from '../log/ILogService'; @@ -24,7 +28,7 @@ import { IShaderModuleService } from '../shader/IShaderModuleService'; import { ISceneService } from './ISceneService'; /** - * will emit `loaded` `resize` `destroy` event + * will emit `loaded` `resize` `destroy` event panstart panmove panend */ @injectable() export default class Scene extends EventEmitter implements ISceneService { @@ -157,6 +161,10 @@ export default class Scene extends EventEmitter implements ISceneService { this.popupService.initPopup(); // 地图初始化之后 才能初始化 container 上的交互 this.interactionService.init(); + this.interactionService.on( + InteractionEvent.Drag, + this.addSceneEvent.bind(this), + ); this.logger.debug(`map ${this.id} loaded`); }); @@ -311,4 +319,8 @@ export default class Scene extends EventEmitter implements ISceneService { this.cameraService.update(viewport); this.render(); }; + + private addSceneEvent(target: IInteractionTarget) { + this.emit(target.type, target); + } } diff --git a/packages/draw/README.md b/packages/draw/README.md index 994ebcb15f..ea24ac57c5 100644 --- a/packages/draw/README.md +++ b/packages/draw/README.md @@ -1,11 +1,9 @@ # `draw` -> TODO: description ## Usage ``` const draw = require('draw'); -// TODO: DEMONSTRATE API ``` diff --git a/packages/draw/rollup.config.js b/packages/draw/rollup.config.js new file mode 100644 index 0000000000..1bedfcb696 --- /dev/null +++ b/packages/draw/rollup.config.js @@ -0,0 +1,33 @@ +import pkg from './package.json'; +import typescript from 'rollup-plugin-typescript'; +import resolve from 'rollup-plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import buble from 'rollup-plugin-buble'; + +export default { + input: './src/index.ts', + plugins: [ + typescript({ + exclude: 'node_modules/**', + typescript: require('typescript') + }), + resolve(), + commonjs(), + buble({ + transforms: { generator: false } + }) + ], + output: [ + { + format: 'cjs', + file: pkg.main, + sourcemap: true + }, + { + format: 'es', + file: pkg.module, + sourcemap: true + } + ] +}; + diff --git a/packages/draw/src/index.ts b/packages/draw/src/index.ts new file mode 100644 index 0000000000..eab6ddf26c --- /dev/null +++ b/packages/draw/src/index.ts @@ -0,0 +1 @@ +export { default as DrawSource } from './source'; diff --git a/packages/draw/src/modes/draw_feature.ts b/packages/draw/src/modes/draw_feature.ts new file mode 100644 index 0000000000..0d7400c1c6 --- /dev/null +++ b/packages/draw/src/modes/draw_feature.ts @@ -0,0 +1,30 @@ +import { Feature, FeatureCollection } from '@turf/helpers'; +import DrawSource from '../source'; + +export interface IDrawOption { + data: FeatureCollection; +} + +export default abstract class DrawFeature { + private source: DrawSource; + constructor(options: IDrawOption) { + const { data } = options; + this.source = new DrawSource(data); + } + + protected onDragStart() { + throw new Error('not imp'); + } + + protected onDragging() { + throw new Error('not imp'); + } + + protected onDragEnd() { + throw new Error('not imp'); + } + + protected onClick() { + throw new Error('not imp'); + } +} diff --git a/packages/draw/src/source.ts b/packages/draw/src/source.ts new file mode 100644 index 0000000000..2a4ea89495 --- /dev/null +++ b/packages/draw/src/source.ts @@ -0,0 +1,41 @@ +import { Feature, FeatureCollection } from '@turf/helpers'; +export default class DrawSource { + private data: FeatureCollection; + constructor(data?: FeatureCollection) { + this.data = data || this.getDefaultData(); + } + + public addFeature(feature: any) { + this.data.features.push(feature); + } + + public getFeature(id: string): Feature | undefined { + const result = this.data.features.find((fe: Feature) => { + return fe?.properties?.id === id; + }); + + return result; + } + public removeFeature(feature: Feature) { + const index = this.getFeatureIndex(feature); + if (index !== undefined) { + this.data.features.splice(index, 1); + } + } + public updateFeature(feature: Feature) { + this.removeFeature(feature); + this.addFeature(feature); + } + private getDefaultData(): FeatureCollection { + return { + type: 'FeatureCollection', + features: [], + }; + } + + private getFeatureIndex(feature: Feature): number | undefined { + return this.data.features.findIndex((fe) => { + return fe?.properties?.id === feature?.properties?.id; + }); + } +} diff --git a/packages/draw/src/util/constant.ts b/packages/draw/src/util/constant.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/draw/src/util/theme.ts b/packages/draw/src/util/theme.ts new file mode 100644 index 0000000000..2bbd9f2990 --- /dev/null +++ b/packages/draw/src/util/theme.ts @@ -0,0 +1,71 @@ +const FillStyle = { + // 正常显示样式 + normal_fill: { + type: 'PolygonLayer', + shape: 'fill', + color: '#3bb2d0', + style: { + opacity: 0.1, + }, + }, + // xai'm'z + active_fill: { + type: 'PolygonLayer', + shape: 'fill', + color: '#fbb03b', + style: { + opacity: 0.1, + }, + }, +}; +const PointStyle = { + normal_point: { + type: 'PointLayer', + shape: 'circle', + color: '#3bb2d0', + size: 3, + style: { + stroke: '#fff', + strokeWidth: 2, + }, + }, + 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, + }, + }, +}; +const LineStyle = { + 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], + }, + }, +}; diff --git a/packages/l7/demo/polygon.html b/packages/l7/demo/polygon.html index bcc6bf0e65..2cd1559adb 100644 --- a/packages/l7/demo/polygon.html +++ b/packages/l7/demo/polygon.html @@ -28,7 +28,7 @@ - + diff --git a/packages/renderer/package.json b/packages/renderer/package.json index 9e9e71ba6a..91520c9bbc 100644 --- a/packages/renderer/package.json +++ b/packages/renderer/package.json @@ -30,7 +30,7 @@ "inversify": "^5.0.1", "lodash": "^4.17.15", "reflect-metadata": "^0.1.13", - "regl": "1.3.11" + "regl": "1.3.13" }, "gitHead": "a5d354b66873f700730248d015c5e539c54b34b7", "publishConfig": { diff --git a/packages/scene/src/index.ts b/packages/scene/src/index.ts index f17bc5989b..91b499142c 100644 --- a/packages/scene/src/index.ts +++ b/packages/scene/src/index.ts @@ -9,6 +9,7 @@ import { IIconFontGlyph, IIconService, IImage, + IInteractionService, ILayer, ILayerService, ILngLat, @@ -56,6 +57,7 @@ class Scene private markerService: IMarkerService; private popupService: IPopupService; private fontService: IFontService; + private interactionService: IInteractionService; private container: Container; @@ -88,7 +90,9 @@ class Scene this.markerService = sceneContainer.get( TYPES.IMarkerService, ); - + this.interactionService = sceneContainer.get( + TYPES.IInteractionService, + ); this.popupService = sceneContainer.get(TYPES.IPopupService); this.initComponent(id); diff --git a/stories/Draw/Components/DrawCircle.tsx b/stories/Draw/Components/DrawCircle.tsx new file mode 100644 index 0000000000..12b8e530b5 --- /dev/null +++ b/stories/Draw/Components/DrawCircle.tsx @@ -0,0 +1,114 @@ +import { LineLayer, PointLayer, PolygonLayer, Popup, Scene } from '@antv/l7'; +import { GaodeMap, Mapbox } from '@antv/l7-maps'; +import { lnglatDistance } from '@antv/l7-utils'; +import turfCircle from '@turf/circle'; +import * as React from 'react'; +const createGeoJSONCircle = ( + center: [number, number], + radiusInKm: number, + points: number = 64, +) => { + const options = { steps: 64 }; + const circle = turfCircle(center, radiusInKm, options); + + return { + type: 'geojson', + data: { + type: 'FeatureCollection', + features: [circle], + }, + }; +}; +export default class MultiPolygon extends React.Component { + private gui: dat.GUI; + private $stats: Node; + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'normal', + center: [121.775374, 31.31067], + zoom: 15, + }), + }); + this.scene = scene; + + scene.on('loaded', () => { + let startPoint = {}; + const circleLayer = new PolygonLayer() + .source({ + type: 'FeatureCollection', + features: [], + }) + .color('#fbb03b') + .shape('fill') + .style({ + opacity: 0.6, + }); + scene.addLayer(circleLayer); + scene.on('dragstart', (e: any) => { + // @ts-ignore + scene.map.dragdrag.disable(); + startPoint = e.lngLat; + const layer = new PointLayer() + .source([startPoint], { + parser: { + type: 'json', + x: 'lng', + y: 'lat', + }, + }) + .shape('circle') + .color('#fbb03b') + .size(5) + .style({ + stroke: '#fff', + strokeWidth: 2, + }); + scene.addLayer(layer); + }); + scene.on('drag', (e: any) => { + // @ts-ignore + const start = [startPoint.lng, startPoint.lat]; + const dis = lnglatDistance( + // @ts-ignore + start, + [e.lngLat.lng, e.lngLat.lat], + ); + const circleData = createGeoJSONCircle( + start as [number, number], + dis / 1000, + ); + circleLayer.setData(circleData.data); + const popup = new Popup().setText(`${dis}`).setLnglat(e.lngLat); + scene.addPopup(popup); + }); + scene.on('dragend', (e: any) => { + // @ts-ignore + scene.map.dragdrag.enable(); + }); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/Draw/Components/DrawRect.tsx b/stories/Draw/Components/DrawRect.tsx new file mode 100644 index 0000000000..d6f18c8a07 --- /dev/null +++ b/stories/Draw/Components/DrawRect.tsx @@ -0,0 +1,126 @@ +import { LineLayer, PointLayer, PolygonLayer, Popup, Scene } from '@antv/l7'; +import { GaodeMap, Mapbox } from '@antv/l7-maps'; +import * as React from 'react'; +const createGeoJSONRect = ( + point1: [number, number], + point2: [number, number], +) => { + const minX = Math.min(point1[0], point2[0]); + const minY = Math.min(point1[1], point2[1]); + const maxX = Math.max(point1[0], point2[0]); + const maxY = Math.max(point1[1], point2[1]); + + return { + type: 'geojson', + data: { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: {}, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [minX, minY], + [minX, maxY], + [maxX, maxY], + [maxX, minY], + [minX, minY], + ], + ], + }, + }, + ], + }, + }; +}; +export default class MultiPolygon extends React.Component { + private gui: dat.GUI; + private $stats: Node; + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new Mapbox({ + pitch: 0, + style: 'normal', + center: [121.775374, 31.31067], + zoom: 15, + }), + }); + this.scene = scene; + + scene.on('loaded', () => { + let startPoint = {}; + const circleLayer = new PolygonLayer() + .source({ + type: 'FeatureCollection', + features: [], + }) + .color('#fbb03b') + .shape('fill') + .style({ + opacity: 0.6, + }); + scene.addLayer(circleLayer); + scene.on('panstart', (e: any) => { + // @ts-ignore + scene.map.dragPan.disable(); + startPoint = e.lngLat; + const layer = new PointLayer() + .source([startPoint], { + parser: { + type: 'json', + x: 'lng', + y: 'lat', + }, + }) + .shape('circle') + .color('#fbb03b') + .size(5) + .style({ + stroke: '#fff', + strokeWidth: 2, + }); + scene.addLayer(layer); + }); + scene.on('panmove', (e: any) => { + // @ts-ignore + const start = [startPoint.lng, startPoint.lat]; + + const circleData = createGeoJSONRect(start as [number, number], [ + e.lngLat.lng, + e.lngLat.lat, + ]); + circleLayer.setData(circleData.data); + // const popup = new Popup().setText(`${dis}`).setLnglat(e.lngLat); + // scene.addPopup(popup); + }); + scene.on('panend', (e: any) => { + // @ts-ignore + scene.map.dragPan.enable(); + }); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/Draw/Draw.stories.tsx b/stories/Draw/Draw.stories.tsx index bc11103b9f..b8a5f9288c 100644 --- a/stories/Draw/Draw.stories.tsx +++ b/stories/Draw/Draw.stories.tsx @@ -1,5 +1,10 @@ import { storiesOf } from '@storybook/react'; import * as React from 'react'; +import DrawCircle from './Components/DrawCircle'; import DrawPolygon from './Components/DrawPolygon'; +import DrawRect from './Components/DrawRect'; -storiesOf('绘制', module).add('绘制面', () => , {}); +storiesOf('绘制', module) + .add('绘制圆', () => , {}) + .add('四边形', () => , {}) + .add('绘制面', () => , {}); diff --git a/yarn.lock b/yarn.lock index ea0a47f28d..800776778a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19419,7 +19419,7 @@ react-docgen@^5.0.0: node-dir "^0.1.10" strip-indent "^3.0.0" -react-dom@^16.10.2, react-dom@^16.12.0, react-dom@^16.8.3, react-dom@^16.9.0: +react-dom@^16.12.0, react-dom@^16.8.3, react-dom@^16.9.0: version "16.12.0" resolved "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz#0da4b714b8d13c2038c9396b54a92baea633fe11" integrity sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw== @@ -20092,10 +20092,10 @@ regjsparser@^0.6.0: dependencies: jsesc "~0.5.0" -regl@^1.3.11: +regl@1.3.13: version "1.3.13" - resolved "https://registry.npmjs.org/regl/-/regl-1.3.13.tgz#c376bfa6477995a9be9cd21495a0c9beb9b2f3af" - integrity sha512-TTiCabJbbUykCL4otjqOvKqDFJhvJOT7xB51JxcDeSHGrEJl1zz4RthPcoOogqfuR3ECN4Te790DfHCXzli5WQ== + resolved "https://registry.npm.alibaba-inc.com/regl/download/regl-1.3.13.tgz#c376bfa6477995a9be9cd21495a0c9beb9b2f3af" + integrity sha1-w3a/pkd5lam+nNIUlaDJvrmy868= relateurl@0.2.x, relateurl@^0.2.7: version "0.2.7"