feat: add edit mode

This commit is contained in:
thinkinggis 2020-03-23 23:26:41 +08:00
parent f3dbcea7ba
commit 0452a6191e
11 changed files with 556 additions and 123 deletions

View File

@ -12,7 +12,9 @@ import {
import turfCircle from '@turf/circle'; import turfCircle from '@turf/circle';
import turfDistance from '@turf/distance'; import turfDistance from '@turf/distance';
import { Feature, featureCollection, point } from '@turf/helpers'; import { Feature, featureCollection, point } from '@turf/helpers';
import RenderLayer from '../render/render';
import DrawFeature, { IDrawOption } from './draw_feature'; import DrawFeature, { IDrawOption } from './draw_feature';
import DrawSelected from './draw_selected';
let CircleFeatureId = 0; let CircleFeatureId = 0;
export type unitsType = 'degrees' | 'radians' | 'miles' | 'kilometers'; export type unitsType = 'degrees' | 'radians' | 'miles' | 'kilometers';
export interface IDrawCircleOption extends IDrawOption { export interface IDrawCircleOption extends IDrawOption {
@ -28,9 +30,8 @@ export default class DrawCircle extends DrawFeature {
private endPoint: ILngLat; private endPoint: ILngLat;
private dragStartPoint: ILngLat; private dragStartPoint: ILngLat;
// 绘制完成之后显示 // 绘制完成之后显示
private normalLayer: ILayer; private normalLayer: RenderLayer;
private normalLineLayer: ILayer; private selectMode: DrawSelected;
// 编辑过程中显示 // 编辑过程中显示
private centerLayer: ILayer; private centerLayer: ILayer;
private circleLayer: ILayer; private circleLayer: ILayer;
@ -39,15 +40,11 @@ export default class DrawCircle extends DrawFeature {
private popup: IPopup; private popup: IPopup;
constructor(scene: Scene, options: Partial<IDrawCircleOption> = {}) { constructor(scene: Scene, options: Partial<IDrawCircleOption> = {}) {
super(scene, options); super(scene, options);
this.initNormalLayer(); this.normalLayer = new RenderLayer(this);
} }
protected onDragStart = (e: IInteractionTarget) => { protected onDragStart = (e: IInteractionTarget) => {
// @ts-ignore // @ts-ignore
this.scene.map.dragPan.disable();
this.dragStartPoint = e.lngLat; this.dragStartPoint = e.lngLat;
if (this.drawStatus === 'DrawSelected') {
return;
}
this.center = e.lngLat; this.center = e.lngLat;
const centerStyle = this.getStyle('active_point'); const centerStyle = this.getStyle('active_point');
const layer = new PointLayer() const layer = new PointLayer()
@ -63,28 +60,19 @@ export default class DrawCircle extends DrawFeature {
.size(centerStyle.size) .size(centerStyle.size)
.style(centerStyle.style); .style(centerStyle.style);
this.scene.addLayer(layer); this.scene.addLayer(layer);
this.initDrawLayer();
this.centerLayer = layer; this.centerLayer = layer;
this.initDrawLayer();
this.setCursor('grabbing'); this.setCursor('grabbing');
}; };
protected getDefaultOptions() { protected getDefaultOptions() {
return { return {
steps: 4, steps: 64,
units: 'kilometres', units: 'kilometres',
cursor: 'crosshair',
}; };
} }
protected onDragging = (e: IInteractionTarget) => { protected onDragging = (e: IInteractionTarget) => {
if (this.drawStatus === 'DrawSelected') {
const delta = {
lng: e.lngLat.lng - this.dragStartPoint.lng,
lat: e.lngLat.lat - this.dragStartPoint.lat,
};
this.moveCircle(this.currentFeature as Feature, delta);
this.dragStartPoint = e.lngLat;
return;
}
this.endPoint = e.lngLat; this.endPoint = e.lngLat;
const currentData = this.createCircleData(this.center, this.endPoint); const currentData = this.createCircleData(this.center, this.endPoint);
this.updateDrawLayer(currentData); this.updateDrawLayer(currentData);
@ -93,16 +81,16 @@ export default class DrawCircle extends DrawFeature {
}; };
protected onDragEnd = () => { protected onDragEnd = () => {
if (this.drawStatus === 'DrawSelected') {
return;
}
this.source.addFeature(this.currentFeature); this.source.addFeature(this.currentFeature);
// @ts-ignore // @ts-ignore
this.scene.map.dragPan.enable(); this.scene.map.dragPan.enable();
this.popup.remove(); this.popup.remove();
// this.disable(); // 绘制完成进入选中状态
this.addCircleLayerEvent(); this.selectMode = new DrawSelected(this.scene, {});
this.selectMode.setSelectedFeature(this.currentFeature as Feature);
this.removeDrawLayer();
this.drawStatus = 'DrawSelected'; this.drawStatus = 'DrawSelected';
this.disable();
return; return;
}; };
protected onClick = () => { protected onClick = () => {
@ -113,7 +101,7 @@ export default class DrawCircle extends DrawFeature {
return; return;
} }
this.currentFeature = null; this.currentFeature = null;
this.updateNormalLayer(); this.normalLayer.updateData();
this.centerLayer.setData([]); this.centerLayer.setData([]);
this.circleLayer.setData(InitFeature); this.circleLayer.setData(InitFeature);
this.circleLineLayer.setData(InitFeature); this.circleLineLayer.setData(InitFeature);
@ -162,82 +150,21 @@ export default class DrawCircle extends DrawFeature {
this.circleLayer.setData(currentData); this.circleLayer.setData(currentData);
this.circleLineLayer.setData(currentData); this.circleLineLayer.setData(currentData);
} }
private removeDrawLayer() {
this.scene.removeLayer(this.circleLayer);
this.scene.removeLayer(this.circleLineLayer);
this.scene.removeLayer(this.centerLayer);
}
private addDrawPopup(lnglat: ILngLat, dis: number) { private addDrawPopup(lnglat: ILngLat, dis: number) {
const popup = new Popup({ const popup = new Popup({
anchor: 'left', anchor: 'left',
closeButton: false, closeButton: false,
}) })
.setLnglat(lnglat) .setLnglat(lnglat)
.setText(`${dis}`); .setText(`半径:${dis.toFixed(2)}千米`);
this.scene.addPopup(popup); this.scene.addPopup(popup);
this.popup = popup; this.popup = popup;
} }
private initNormalLayer() {
const style = this.getStyle('normal_fill');
const linestyle = this.getStyle('normal_line');
this.normalLayer = new PolygonLayer()
.source(this.source.data)
.shape('fill')
.active(true)
.color(style.color)
.style(style.style);
this.normalLineLayer = new LineLayer()
.source(this.source.data)
.shape('line')
.size(linestyle.size)
.color(linestyle.color)
.style(linestyle.style);
this.scene.addLayer(this.normalLayer);
this.scene.addLayer(this.normalLineLayer);
this.normalLayer.on('click', this.onNormalLayerClick);
}
private updateNormalLayer() {
this.normalLayer.setData(this.source.data);
this.normalLineLayer.setData(this.source.data);
}
private onNormalLayerClick = (e: any) => {
this.currentFeature = e.feature;
this.normalLayer.filter('id', (id: string) => {
return this.currentFeature === null || id !== e.feature.properties.id;
});
this.normalLineLayer.filter('id', (id: string) => {
return this.currentFeature === null || id !== e.feature.properties.id;
});
const seletedFeature = e.feature;
this.setCursor('move');
this.updateDrawLayer(featureCollection([seletedFeature]));
this.centerLayer.setData([seletedFeature.properties.center]);
this.drawStatus = 'DrawSelected';
this.enable();
};
private addCircleLayerEvent() {
this.circleLayer.on('mousemove', (e) => {
this.setCursor('move');
});
this.circleLayer.on('unmousemove', (e) => {
this.resetCursor();
});
this.circleLayer.on('unclick', this.onCircleLayerClick);
}
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.updateDrawLayer(newCircle);
}
} }

View File

@ -0,0 +1,109 @@
import {
IInteractionTarget,
ILayer,
ILngLat,
IPopup,
LineLayer,
PointLayer,
PolygonLayer,
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 DrawFeature, { IDrawOption } from './draw_feature';
let CircleFeatureId = 0;
export type unitsType = 'degrees' | 'radians' | 'miles' | 'kilometers';
export interface IDrawCircleOption extends IDrawOption {
units: unitsType;
steps: number;
}
const InitFeature = {
type: 'FeatureCollection',
features: [],
};
export default class DrawEdit extends DrawFeature {
private center: ILngLat;
private endPoint: ILngLat;
// 绘制完成之后显示
private editLayer: EditRender;
// 编辑过程中显示
private currentFeature: Feature | null;
private popup: IPopup;
constructor(scene: Scene, options: Partial<IDrawCircleOption> = {}) {
super(scene, options);
this.editLayer = new EditRender(this);
}
public setEditFeature(feature: Feature) {
this.currentFeature = feature;
this.center = feature?.properties?.center;
this.editLayer.updateData({
type: 'FeatureCollection',
features: [feature],
});
}
protected onDragStart = (e: IInteractionTarget) => {
// @ts-ignore
};
protected getDefaultOptions() {
return {
steps: 64,
units: 'kilometres',
cursor: 'move',
};
}
protected onDragging = (e: IInteractionTarget) => {
this.endPoint = e.lngLat;
const currentData = this.createCircleData(this.center, this.endPoint);
this.editLayer.updateData(currentData);
this.addDrawPopup(this.endPoint, this.currentFeature?.properties?.radius);
return;
};
protected onDragEnd = () => {
this.popup.remove();
this.disable();
// @ts-ignore
this.scene.map.dragPan.enable();
};
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: `${CircleFeatureId++}`,
radius,
center,
endPoint,
},
});
this.currentFeature = feature as Feature;
return featureCollection([feature]);
}
private addDrawPopup(lnglat: ILngLat, dis: number) {
const popup = new Popup({
anchor: 'left',
closeButton: false,
})
.setLnglat(lnglat)
.setText(`半径:${dis.toFixed(2)}千米`);
this.scene.addPopup(popup);
this.popup = popup;
}
}

View File

@ -17,8 +17,8 @@ export type DrawStatus =
| 'EditFinish'; | 'EditFinish';
export default abstract class DrawFeature { export default abstract class DrawFeature {
protected source: DrawSource; public source: DrawSource;
protected scene: Scene; public scene: Scene;
protected options: { protected options: {
[key: string]: any; [key: string]: any;
} = { } = {
@ -36,20 +36,49 @@ export default abstract class DrawFeature {
if (this.isEnable) { if (this.isEnable) {
return; return;
} }
// @ts-ignore
this.scene.map.dragPan.disable();
this.scene.on('dragstart', this.onDragStart); this.scene.on('dragstart', this.onDragStart);
this.scene.on('dragging', this.onDragging); this.scene.on('dragging', this.onDragging);
this.scene.on('dragend', this.onDragEnd); this.scene.on('dragend', this.onDragEnd);
// this.scene.on('click', this.onClick); this.setCursor(this.getOption('cursor'));
this.setCursor('crosshair'); this.isEnable = true;
} }
public disable() { public disable() {
if (!this.isEnable) {
return;
}
this.scene.off('dragstart', this.onDragStart); this.scene.off('dragstart', this.onDragStart);
this.scene.off('dragging', this.onDragging); this.scene.off('dragging', this.onDragging);
this.scene.off('dragend', this.onDragEnd); this.scene.off('dragend', this.onDragEnd);
// this.scene.off('click', this.onClick); // this.scene.off('click', this.onClick);
this.resetCursor(); this.resetCursor();
// @ts-ignore
this.scene.map.dragPan.enable();
this.isEnable = false;
} }
public getOption(key: string) {
return this.options[key];
}
public getStyle(id: string) {
return this.getOption('style')[id];
}
public setCursor(cursor: string) {
const container = this.scene.getContainer();
if (container) {
container.style.cursor = cursor;
}
}
public resetCursor() {
const container = this.scene.getContainer();
if (container) {
container.style.cursor = 'default';
}
}
protected getDefaultOptions() { protected getDefaultOptions() {
return {}; return {};
} }
@ -61,23 +90,4 @@ export default abstract class DrawFeature {
protected abstract onDragEnd(e: IInteractionTarget): void; protected abstract onDragEnd(e: IInteractionTarget): void;
protected abstract onClick(): void; protected abstract onClick(): void;
protected getOption(key: string) {
return this.options[key];
}
protected getStyle(id: string) {
return this.getOption('style')[id];
}
protected setCursor(cursor: string) {
const container = this.scene.getContainer();
if (container) {
container.style.cursor = cursor;
}
}
protected resetCursor() {
const container = this.scene.getContainer();
if (container) {
container.style.cursor = 'default';
}
}
} }

View File

@ -0,0 +1,114 @@
import {
IInteractionTarget,
ILayer,
ILngLat,
IPopup,
LineLayer,
PointLayer,
PolygonLayer,
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 DrawFeature, { IDrawOption } from './draw_feature';
let CircleFeatureId = 0;
export type unitsType = 'degrees' | 'radians' | 'miles' | 'kilometers';
export interface IDrawCircleOption extends IDrawOption {
units: unitsType;
steps: number;
}
const InitFeature = {
type: 'FeatureCollection',
features: [],
};
export default class DrawSelect extends DrawFeature {
private center: ILngLat;
private dragStartPoint: ILngLat;
// 绘制完成之后显示
private editLayer: EditRender;
// 编辑过程中显示
private currentFeature: Feature | null;
constructor(scene: Scene, options: Partial<IDrawCircleOption> = {}) {
super(scene, options);
this.editLayer = new EditRender(this);
}
public setSelectedFeature(feature: Feature) {
this.currentFeature = feature;
this.editLayer.updateData({
type: 'FeatureCollection',
features: [feature],
});
}
protected onDragStart = (e: IInteractionTarget) => {
// @ts-ignore
this.scene.map.dragPan.disable();
this.dragStartPoint = e.lngLat;
};
protected getDefaultOptions() {
return {
steps: 64,
units: 'kilometres',
cursor: 'move',
};
}
protected onDragging = (e: IInteractionTarget) => {
const delta = {
lng: e.lngLat.lng - this.dragStartPoint.lng,
lat: e.lngLat.lat - this.dragStartPoint.lat,
};
this.moveCircle(this.currentFeature as Feature, delta);
this.dragStartPoint = e.lngLat;
return;
};
protected onDragEnd = () => {
return null;
};
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: `${CircleFeatureId++}`,
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);
}
}

View File

View File

@ -0,0 +1,107 @@
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';
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);
this.addLayerEvent();
}
public updateData(data: any) {
this.currentFeature = data.features[0];
this.lineLayer.setData(data);
this.polygonLayer.setData(data);
this.centerLayer.setData([data.features[0].properties.center]);
this.endPointLayer.setData([data.features[0].properties.endPoint]);
}
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);
}
private addLayerEvent() {
this.endPointLayer.on('mousemove', (e) => {
this.draw.setCursor('move');
this.draw.enable();
});
this.endPointLayer.on('unmousemove', (e) => {
this.draw.resetCursor();
this.draw.disable();
});
}
}

View File

@ -0,0 +1,57 @@
import {
IInteractionTarget,
ILayer,
ILngLat,
IPopup,
LineLayer,
PointLayer,
PolygonLayer,
Popup,
Scene,
} from '@antv/l7';
const InitFeature = {
type: 'FeatureCollection',
features: [],
};
import Draw from '../modes/draw_feature';
export default class RenderLayer {
private polygonLayer: ILayer;
private lineLayer: ILayer;
private draw: Draw;
constructor(draw: Draw) {
this.draw = draw;
this.init();
}
public init() {
const style = this.draw.getStyle('normal_fill');
const linestyle = this.draw.getStyle('normal_line');
this.polygonLayer = new PolygonLayer({
zIndex: 0,
})
.source(InitFeature)
.shape('fill')
.active(true)
.color(style.color)
.style(style.style);
this.lineLayer = new LineLayer({
zIndex: 1,
})
.source(InitFeature)
.shape('line')
.size(linestyle.size)
.color(linestyle.color)
.style(linestyle.style);
this.draw.scene.addLayer(this.polygonLayer);
this.draw.scene.addLayer(this.lineLayer);
}
public updateData() {
this.lineLayer.setData(this.draw.source.data);
this.polygonLayer.setData(this.draw.source.data);
}
public destroy() {
this.draw.scene.removeLayer(this.lineLayer);
this.draw.scene.removeLayer(this.polygonLayer);
}
}

View File

@ -0,0 +1,99 @@
import {
IInteractionTarget,
ILayer,
ILngLat,
IPopup,
LineLayer,
PointLayer,
PolygonLayer,
Popup,
Scene,
} from '@antv/l7';
const InitFeature = {
type: 'FeatureCollection',
features: [],
};
import { Feature } from '@turf/helpers';
import DrawEdit from '../modes/draw_edit';
import Draw from '../modes/draw_feature';
export default class EditRenderLayer {
private polygonLayer: ILayer;
private lineLayer: ILayer;
private centerLayer: 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.draw.scene.addLayer(this.polygonLayer);
this.draw.scene.addLayer(this.lineLayer);
this.draw.scene.addLayer(this.centerLayer);
this.addLayerEvent();
}
public updateData(data: any) {
this.currentFeature = data.features[0];
this.lineLayer.setData(data);
this.polygonLayer.setData(data);
this.centerLayer.setData([data.features[0].properties.center]);
}
public destroy() {
this.draw.scene.removeLayer(this.lineLayer);
this.draw.scene.removeLayer(this.polygonLayer);
this.draw.scene.removeLayer(this.centerLayer);
}
private addLayerEvent() {
this.polygonLayer.on('mousemove', (e) => {
this.draw.setCursor('move');
this.draw.enable();
});
this.polygonLayer.on('unmousemove', (e) => {
this.draw.resetCursor();
this.draw.disable();
});
this.polygonLayer.on('click', (e) => {
// 进入编辑态
const drawEdit = new DrawEdit(this.draw.scene, {});
drawEdit.setEditFeature(this.currentFeature);
this.draw.disable();
this.destroy();
});
this.polygonLayer.on('unclick', (e) => {
// 取消选中 回到初始态
});
}
}

View File

@ -30,7 +30,7 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
normal: { normal: {
blend: 'additive', blend: 'additive',
}, },
fill: {}, fill: { blend: 'normal' },
extrude: {}, extrude: {},
image: {}, image: {},
icon: {}, icon: {},

View File

@ -15,9 +15,9 @@ export default class Circle extends React.Component {
id: 'map', id: 'map',
map: new Mapbox({ map: new Mapbox({
pitch: 0, pitch: 0,
style: 'normal', style: 'light',
center: [121.775374, 31.31067], center: [113.775374, 28.31067],
zoom: 15, zoom: 12,
}), }),
}); });
this.scene = scene; this.scene = scene;

View File

@ -44,7 +44,17 @@ export default class HexagonLayerDemo extends React.Component {
], ],
}) })
.shape('hexagon') // 支持 circle, hexagon,triangle .shape('hexagon') // 支持 circle, hexagon,triangle
.color('mode', ['#ffffe5','#fff7bc','#fee391','#fec44f','#fe9929','#ec7014','#cc4c02','#993404','#662506']) .color('mode', [
'#ffffe5',
'#fff7bc',
'#fee391',
'#fec44f',
'#fe9929',
'#ec7014',
'#cc4c02',
'#993404',
'#662506',
])
.active(false) .active(false)
.style({ .style({
coverage: 0.7, coverage: 0.7,