Merge pull request #257 from antvis/l7-draw

L7 draw
This commit is contained in:
@thinkinggis 2020-03-20 11:49:25 +08:00 committed by GitHub
commit ccf63d081c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 588 additions and 24959 deletions

View File

@ -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,
// };

View File

@ -30,4 +30,4 @@ export default function glsl(include, minify) {
};
}
};
}
}

View File

@ -4,6 +4,7 @@ export enum InteractionEvent {
Click = 'click',
Select = 'select',
Active = 'active',
Drag = 'drag',
}
export interface IInteractionTarget {
x: number;

View File

@ -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
@ -53,20 +59,21 @@ export default class InteractionService extends EventEmitter
private addEventListenerOnMap() {
const $containter = this.mapService.getMapContainer();
if ($containter) {
const hammertime = new Hammer($containter);
hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL });
hammertime.get('pinch').set({ enable: true });
// hammertime.on('panstart', this.onPanstart);
// hammertime.on('panmove', this.onPanmove);
// hammertime.on('panend', this.onPanend);
// hammertime.on('pinch', this.onPinch);
$containter.addEventListener('touchstart', this.onTouch);
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.Pan({ threshold: 0, pointers: 0 }));
hammertime.add(new Hammer.Press({}));
// 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 pancancel', this.onDrag);
// hammertime.on('press pressup', this.onHammer);
// $containter.addEventListener('touchstart', this.onTouch);
$containter.addEventListener('mousemove', this.onHover);
$containter.addEventListener('click', this.onHover);
// $containter.addEventListener('click', this.onHover);
$containter.addEventListener('mousedown', this.onHover);
$containter.addEventListener('mouseup', this.onHover);
// $containter.addEventListener('dblclick', this.onHover);
$containter.addEventListener('contextmenu', this.onHover);
this.hammertime = hammertime;
@ -79,28 +86,47 @@ export default class InteractionService extends EventEmitter
const $containter = this.mapService.getMapContainer();
if ($containter) {
$containter.removeEventListener('mousemove', this.onHover);
$containter.removeEventListener('touchstart', this.onTouch);
$containter.removeEventListener('click', 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);
$containter.removeEventListener('mouseup', this.onHover);
// $containter.removeEventListener('dblclick', this.onHover);
$containter.removeEventListener('contextmenu', this.onHover);
}
}
private onTouch = (target: TouchEvent) => {
target.stopPropagation();
if (target.targetTouches.length > 1) {
return;
}
const touch = target.targetTouches[0];
// @ts-ignore
this.onHover({
x: touch.pageX,
y: touch.pageY,
type: 'touch',
});
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 = Math.floor(target.pointers[0].clientY);
clientX = Math.floor(target.pointers[0].clientX);
} else {
clientY = Math.floor((target.srcEvent as MouseEvent).y);
clientX = Math.floor((target.srcEvent as MouseEvent).x);
}
const $containter = this.mapService.getMapContainer();
if ($containter) {
const { top, left } = $containter.getBoundingClientRect();
clientX -= left;
clientY -= top;
}
const lngLat = this.mapService.containerToLngLat([clientX, clientY]);
return { x: clientX, y: clientY, lngLat, type };
}
private onHover = ({ x, y, type }: MouseEvent) => {
const $containter = this.mapService.getMapContainer();
if ($containter) {
@ -122,7 +148,12 @@ export default class InteractionService extends EventEmitter
return;
}
if (type !== 'click' || type !== 'click') {
this.emit(InteractionEvent.Hover, { x, y, lngLat, type });
this.emit(InteractionEvent.Hover, {
x,
y,
lngLat,
type,
});
}
};

View File

@ -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),

View File

@ -22,4 +22,8 @@ export const SceneEventList: string[] = [
'maploaded',
'resize',
'destroy',
'dragstart',
'dragging',
'dragend',
'dragcancel',
];

View File

@ -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);
}
}

9
packages/draw/README.md Normal file
View File

@ -0,0 +1,9 @@
# `draw`
## Usage
```
const draw = require('draw');
```

View File

@ -0,0 +1,38 @@
{
"name": "@antv/l7-draw",
"version": "2.1.3",
"description": "Now Im the model of a modern major general / The venerated Virginian veteran whose men are all / Lining up, to put me up on a pedestal / Writin letters to relatives / Embellishin my elegance and eloquence / But the elephant is in the room / The truth is in ya face when ya hear the British cannons go / BOOM",
"keywords": [],
"author": "thinkinggis <lzx199065@gmail.com>",
"license": "ISC",
"main": "lib/index.js",
"module": "es/index.js",
"types": "es/index.d.ts",
"sideEffects": true,
"files": [
"lib",
"es",
"README.md"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/antvis/L7.git"
},
"scripts": {
"tsc": "tsc --project tsconfig.build.json",
"clean": "rimraf dist; rimraf es; rimraf lib;",
"build": "run-p build:*",
"build:cjs": "BABEL_ENV=cjs babel src --root-mode upward --out-dir lib --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
"build:esm": "BABEL_ENV=esm babel src --root-mode upward --out-dir es --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
"watch": "BABEL_ENV=cjs babel src --watch --root-mode upward --out-dir lib --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
"lint:ts": "run-p -c lint:ts-*",
"test": "jest"
},
"bugs": {
"url": "https://github.com/antvis/L7/issues"
},
"homepage": "https://github.com/antvis/L7#readme"
}

View File

@ -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
}
]
};

View File

@ -0,0 +1 @@
export { default as DrawSource } from './source';

View File

@ -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');
}
}

View File

@ -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;
});
}
}

View File

View File

@ -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],
},
},
};

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"declarationDir": "./es",
"rootDir": "./src",
"baseUrl": "./"
},
"include": ["./src"]
}

View File

@ -27,7 +27,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.8.3/polyfill.min.js"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/v1.8.0/mapbox-gl.js"></script>
<script src="../dist/l7.js"></script>
<script src="../dist/l7-dev.js"></script>
<script>
console.log(L7);
const scene = new L7.Scene({

View File

@ -28,7 +28,7 @@
<!-- <script src="https://cdn.jsdelivr.net/npm/symbol-es6@0.1.2/symbol-es6.min.js"></script> -->
<script src="https://api.mapbox.com/mapbox-gl-js/v1.8.0/mapbox-gl.js"></script>
<script src="../dist/l7.js"></script>
<script src="../dist/l7-dev.js"></script>
<script>
const data =
{"type":"FeatureCollection","features":[
@ -96,7 +96,7 @@
})
})
scene.on('loaded',()=>{
alert('加载完成');
console.log(L7);
const color = [ 'rgb(255,255,217)', 'rgb(237,248,177)', 'rgb(199,233,180)', 'rgb(127,205,187)', 'rgb(65,182,196)', 'rgb(29,145,192)', 'rgb(34,94,168)', 'rgb(12,44,132)' ];
@ -119,7 +119,7 @@
.style({
opacity: 1.0
});
scene.addLayer(layer);
scene.addLayer(layer);
});
</script>
</body>

View File

@ -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": {

View File

@ -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<IMarkerService>(
TYPES.IMarkerService,
);
this.interactionService = sceneContainer.get<IInteractionService>(
TYPES.IInteractionService,
);
this.popupService = sceneContainer.get<IPopupService>(TYPES.IPopupService);
this.initComponent(id);

View File

@ -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 (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -1,5 +1,5 @@
import { LineLayer, PointLayer, PolygonLayer, Scene } from '@antv/l7';
import { Mapbox, GaodeMap } from '@antv/l7-maps';
import { GaodeMap, Mapbox } from '@antv/l7-maps';
import * as React from 'react';
function convertRGB2Hex(rgb: number[]) {
@ -83,6 +83,12 @@ export default class MultiPolygon extends React.Component {
.style({
opacity: 0.1,
});
layer.on('click', (e) => {
console.log(e);
});
layer.on('dblclick', (e) => {
console.log(e);
});
const line = new PolygonLayer()
.source(data)
.shape('line')
@ -116,6 +122,7 @@ export default class MultiPolygon extends React.Component {
scene.addLayer(this.addPoint(data2));
scene.addLayer(this.addActivePoint());
scene.addLayer(this.addMidPoint(data2));
this.scene = scene;
}
public render() {
return (

View File

@ -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 (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -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('绘制面', () => <DrawPolygon />, {});
storiesOf('绘制', module)
.add('绘制圆', () => <DrawCircle />, {})
.add('四边形', () => <DrawRect />, {})
.add('绘制面', () => <DrawPolygon />, {});

View File

@ -38,18 +38,6 @@ export default React.memo(function Map() {
bottom: 0,
}}
>
<SceneContext.Consumer>
{(scene) => {
console.log(scene);
return null;
}}
</SceneContext.Consumer>
<SceneEvent
type="click"
handler={() => {
console.log('click');
}}
/>
<Popup lnglat={[110.1938, 50.25] as number[]}>
<p>122222</p>
</Popup>

View File

@ -30,6 +30,9 @@ export default React.memo(function Map() {
bottom: 0,
}}
>
<Popup lnglat={[110.1938, 50.25] as number[]}>
<p>122222</p>
</Popup>
{data && [
<PolygonLayer
key={'2'}
@ -54,6 +57,7 @@ export default React.memo(function Map() {
opacity: 1,
}}
/>,
<LineLayer
key={'21'}
source={{

View File

@ -107,7 +107,7 @@ export default React.memo(function Map() {
zoom: 1,
}}
style={{
position: 'absolute',
// position: 'absolute',
top: 0,
left: 0,
right: 0,

24907
yarn.lock

File diff suppressed because it is too large Load Diff