tmp-react-map-gl-draw
Version:
A lite version editing layer with react
302 lines (257 loc) • 9 kB
JavaScript
// @flow
import type {
Feature,
FeatureCollection,
ClickEvent,
StopDraggingEvent,
PointerMoveEvent,
Position
} from '@nebula.gl/edit-modes';
import type { ModeProps } from '../types';
import { RENDER_TYPE, EDIT_TYPE, ELEMENT_TYPE, GEOJSON_TYPE, GUIDE_TYPE } from '../constants';
import BaseMode from './base-mode';
import {
findClosestPointOnLineSegment,
getFeatureCoordinates,
isNumeric,
updateRectanglePosition
} from './utils';
export default class EditingMode extends BaseMode {
handleClick = (event: ClickEvent, props: ModeProps<FeatureCollection>) => {
const pickedObject = event.picks && event.picks[0] && event.picks[0].object;
const selectedFeatureIndex = props.selectedIndexes && props.selectedIndexes[0];
if (!pickedObject || pickedObject.featureIndex !== selectedFeatureIndex) {
return;
}
const { featureIndex, index } = pickedObject;
const feature = this.getSelectedFeature(props, featureIndex);
if (
feature &&
(feature.geometry.type === RENDER_TYPE.POLYGON ||
feature.geometry.type === RENDER_TYPE.LINE_STRING) &&
pickedObject.type === ELEMENT_TYPE.SEGMENT
) {
const coordinates = getFeatureCoordinates(feature);
if (!coordinates) {
return;
}
const insertIndex = (index + 1) % coordinates.length;
const positionIndexes =
feature.geometry.type === RENDER_TYPE.POLYGON ? [0, insertIndex] : [insertIndex];
const insertMapCoords = this._getPointOnSegment(feature, pickedObject, event.mapCoords);
const updatedData = props.data
.addPosition(featureIndex, positionIndexes, insertMapCoords)
.getObject();
props.onEdit({
editType: EDIT_TYPE.ADD_POSITION,
updatedData,
editContext: [
{
featureIndex,
editHandleIndex: insertIndex,
screenCoords: props.viewport && props.viewport.project(insertMapCoords),
mapCoords: insertMapCoords
}
]
});
}
};
handleStopDragging(event: StopDraggingEvent, props: ModeProps<FeatureCollection>) {
// replace point
const pickedObject = event.picks && event.picks[0] && event.picks[0].object;
if (!pickedObject || !isNumeric(pickedObject.featureIndex)) {
return;
}
switch (pickedObject.type) {
case ELEMENT_TYPE.FEATURE:
case ELEMENT_TYPE.EDIT_HANDLE:
this._handleDragging(event, props);
break;
default:
}
}
_handleDragging = (
event: PointerMoveEvent | StopDraggingEvent,
props: ModeProps<FeatureCollection>
) => {
const { onEdit } = props;
const selectedFeature = this.getSelectedFeature(props);
// nothing clicked
const { isDragging, pointerDownPicks, screenCoords } = event;
const { lastPointerMoveEvent } = props;
const clickedObject = pointerDownPicks && pointerDownPicks[0] && pointerDownPicks[0].object;
if (!clickedObject || !isNumeric(clickedObject.featureIndex)) {
return;
}
const editHandleIndex = clickedObject.index;
console.log('editmode _handleDragging', selectedFeature, event, )
// not dragging
let updatedData = null;
const editType = isDragging ? EDIT_TYPE.MOVE_POSITION : EDIT_TYPE.FINISH_MOVE_POSITION;
switch (clickedObject.type) {
case ELEMENT_TYPE.FEATURE:
case ELEMENT_TYPE.FILL:
case ELEMENT_TYPE.SEGMENT:
// dragging feature
const dx = screenCoords[0] - lastPointerMoveEvent.screenCoords[0];
const dy = screenCoords[1] - lastPointerMoveEvent.screenCoords[1];
updatedData = this._updateFeature(props, 'feature', { dx, dy });
console.log('editmode updateData', updatedData)
onEdit({
editType,
updatedData,
editContext: null
});
break;
case ELEMENT_TYPE.EDIT_HANDLE:
// dragging editHandle
// dragging rectangle or other shapes
const updateType =
selectedFeature.properties.renderType === RENDER_TYPE.RECTANGLE
? 'rectangle'
: 'editHandle';
updatedData = this._updateFeature(props, updateType, {
editHandleIndex,
mapCoords: event.mapCoords
});
onEdit({
editType,
updatedData,
editContext: null
});
break;
default:
}
};
handlePointerMove = (event: PointerMoveEvent, props: ModeProps<FeatureCollection>) => {
// no selected feature
const selectedFeature = this.getSelectedFeature(props);
if (!selectedFeature) {
return;
}
if (!event.isDragging) {
return;
}
this._handleDragging(event, props);
};
// TODO - refactor
_updateFeature = (props: ModeProps<FeatureCollection>, type: string, options: any = {}) => {
const { data, selectedIndexes, viewport } = props;
const featureIndex = selectedIndexes && selectedIndexes[0];
const feature = this.getSelectedFeature(props, featureIndex);
let geometry = null;
const coordinates = getFeatureCoordinates(feature);
if (!coordinates) {
return null;
}
let newCoordinates = [...coordinates];
switch (type) {
case 'editHandle':
const positionIndexes =
feature.geometry.type === GEOJSON_TYPE.POLYGON
? [0, options.editHandleIndex]
: [options.editHandleIndex];
return data.replacePosition(featureIndex, positionIndexes, options.mapCoords).getObject();
case 'feature':
const { dx, dy } = options;
newCoordinates = newCoordinates
.map(mapCoords => {
const pixels = viewport && viewport.project(mapCoords);
if (pixels) {
pixels[0] += dx;
pixels[1] += dy;
return viewport && viewport.unproject(pixels);
}
return null;
})
.filter(Boolean);
geometry = {
type: feature.geometry.type,
coordinates:
feature.geometry.type === GEOJSON_TYPE.POLYGON ? [newCoordinates] : newCoordinates
};
return data.replaceGeometry(featureIndex, geometry).getObject();
case 'rectangle':
// moved editHandleIndex and destination mapCoords
newCoordinates = updateRectanglePosition(
feature,
options.editHandleIndex,
options.mapCoords
);
geometry = {
type: GEOJSON_TYPE.POLYGON,
coordinates: newCoordinates
};
return data.replaceGeometry(featureIndex, geometry).getObject();
default:
return data && data.getObject();
}
};
_getPointOnSegment(feature: Feature, pickedObject: any, pickedMapCoords: Position) {
const coordinates = getFeatureCoordinates(feature);
if (!coordinates) {
return null;
}
const srcVertexIndex = pickedObject.index;
const targetVertexIndex = pickedObject.index + 1;
return findClosestPointOnLineSegment(
coordinates[srcVertexIndex],
coordinates[targetVertexIndex],
pickedMapCoords
);
}
_getCursorEditHandle = (event: PointerMoveEvent, feature: Feature) => {
const { isDragging, picks } = event;
// if not pick segment
const pickedObject = picks && picks[0] && picks[0].object;
if (
!pickedObject ||
!isNumeric(pickedObject.featureIndex) ||
pickedObject.type !== ELEMENT_TYPE.SEGMENT
) {
return null;
}
// if dragging or feature is neither polygon nor line string
if (
isDragging ||
(feature.properties.renderType !== GEOJSON_TYPE.POLYGON &&
feature.properties.renderType !== GEOJSON_TYPE.LINE_STRING)
) {
return null;
}
const insertMapCoords = this._getPointOnSegment(feature, pickedObject, event.mapCoords);
if (!insertMapCoords) {
return null;
}
return {
type: 'Feature',
properties: {
guideType: GUIDE_TYPE.CURSOR_EDIT_HANDLE,
renderType: feature.properties.renderType,
positionIndexes: [null]
},
geometry: {
type: GEOJSON_TYPE.POINT,
coordinates: [insertMapCoords]
}
};
};
getGuides = (props: ModeProps<FeatureCollection>) => {
const selectedFeature = this.getSelectedFeature(props);
const selectedFeatureIndex = props.selectedIndexes && props.selectedIndexes[0];
if (!selectedFeature || selectedFeature.geometry.type === GEOJSON_TYPE.POINT) {
return null;
}
const event = props.lastPointerMoveEvent;
// feature editHandles
const editHandles = this.getEditHandlesFromFeature(selectedFeature, selectedFeatureIndex) || [];
// cursor editHandle
const cursorEditHandle = this._getCursorEditHandle(event, selectedFeature);
if (cursorEditHandle) {
editHandles.push(this._getCursorEditHandle(event, selectedFeature));
}
return {
editHandles: editHandles.length ? editHandles : null
};
};
}