Skip to content

Commit

Permalink
Measure distance mode + tooltips (#299)
Browse files Browse the repository at this point in the history
  • Loading branch information
supersonicclay committed Nov 28, 2019
1 parent 8dae1f0 commit 9b60f00
Show file tree
Hide file tree
Showing 14 changed files with 639 additions and 21 deletions.
4 changes: 2 additions & 2 deletions docs/api-reference/layers/editable-geojson-layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,11 @@ Edit handle objects can be represented by either points or icons. `editHandlePoi

#### `editHandlePointOutline` (Boolean, optional)

* Default: `false`
* Default: `true`

#### `editHandlePointStrokeWidth` (Number, optional)

* Default: `1`
* Default: `2`

#### `editHandlePointRadiusMinPixels` (Number, optional)

Expand Down
20 changes: 20 additions & 0 deletions docs/api-reference/modes/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,26 @@ User can split a polygon by drawing a new `LineString` feature on top of the pol

* If the clicked position is inside the polygon, it will not split the polygon

## [MeasureDistanceMode](https://github.com/uber/nebula.gl/blob/master/modules/edit-modes/src/lib/measure-distance-mode.js)

User can measure a distance between two points.

### ModeConfig

The following options can be provided in the `modeConfig` object:

* `turfOptions` (Object, optional)
* `options` object passed to turf's [distance](https://turfjs.org/docs/#distance) function
* Default: `undefined`

* `formatTooltip` (Function, optional)
* Function to format tooltip text (argument is the numeric distance)
* Default: `(distance) => parseFloat(distance).toFixed(2) + units`

* `measurementCallback` (Function, optional)
* Function to call as measurements are calculated
* Default: `undefined`

## [ElevationMode](https://github.com/uber/nebula.gl/blob/master/modules/edit-modes/src/lib/elevation-mode.js)

User can move a point up and down.
Expand Down
70 changes: 54 additions & 16 deletions examples/advanced/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
DrawEllipseUsingThreePointsMode,
DrawRectangleUsingThreePointsMode,
Draw90DegreePolygonMode,
MeasureDistanceMode,
ViewMode,
CompositeMode,
SnappableMode,
Expand Down Expand Up @@ -78,19 +79,9 @@ const initialViewport = {
const ALL_MODES = [
{
category: 'View',
modes: [{ label: 'View', mode: ViewMode }]
},
{
category: 'Alter',
modes: [
{ label: 'Modify', mode: ModifyMode },
{ label: 'Elevation', mode: ElevationMode },
{ label: 'Translate', mode: new SnappableMode(new TranslateMode()) },
{ label: 'Rotate', mode: RotateMode },
{ label: 'Scale', mode: ScaleMode },
{ label: 'Duplicate', mode: DuplicateMode },
{ label: 'Extrude', mode: ExtrudeMode },
{ label: 'Split', mode: SplitPolygonMode }
{ label: 'View', mode: ViewMode },
{ label: 'Measure Distance', mode: MeasureDistanceMode }
]
},
{
Expand All @@ -108,6 +99,19 @@ const ALL_MODES = [
{ label: 'Draw Ellipse Using 3 Points', mode: DrawEllipseUsingThreePointsMode }
]
},
{
category: 'Alter',
modes: [
{ label: 'Modify', mode: ModifyMode },
{ label: 'Elevation', mode: ElevationMode },
{ label: 'Translate', mode: new SnappableMode(new TranslateMode()) },
{ label: 'Rotate', mode: RotateMode },
{ label: 'Scale', mode: ScaleMode },
{ label: 'Duplicate', mode: DuplicateMode },
{ label: 'Extrude', mode: ExtrudeMode },
{ label: 'Split', mode: SplitPolygonMode }
]
},
{
category: 'Composite',
modes: [{ label: 'Draw LineString + Modify', mode: COMPOSITE_MODE }]
Expand Down Expand Up @@ -525,6 +529,32 @@ export default class Example extends Component<
);
}

_renderMeasureDistanceControls() {
return (
<ToolboxRow key="measure-distance">
<ToolboxTitle>Units</ToolboxTitle>
<ToolboxControl>
<select
value={
(this.state.modeConfig &&
this.state.modeConfig.turfOptions &&
this.state.modeConfig.turfOptions.units) ||
'kilometers'
}
onChange={event =>
this.setState({ modeConfig: { turfOptions: { units: event.target.value } } })
}
>
<option value="kilometers">kilometers</option>
<option value="miles">miles</option>
<option value="degrees">degrees</option>
<option value="radians">radians</option>
</select>
</ToolboxControl>
</ToolboxRow>
);
}

_renderModeConfigControls() {
const controls = [];

Expand All @@ -543,6 +573,9 @@ export default class Example extends Component<
if (this.state.mode instanceof SnappableMode) {
controls.push(this._renderSnappingControls());
}
if (this.state.mode === MeasureDistanceMode) {
controls.push(this._renderMeasureDistanceControls());
}

return controls;
}
Expand Down Expand Up @@ -807,9 +840,14 @@ export default class Example extends Component<
}

// Demonstrate how to override sub layer properties
let _subLayerProps = null;
let _subLayerProps = {
tooltips: {
getColor: [255, 255, 255, 255]
}
};

if (this.state.editHandleType === 'elevated') {
_subLayerProps = {
_subLayerProps = Object.assign(_subLayerProps, {
guides: {
_subLayerProps: {
points: {
Expand All @@ -818,11 +856,11 @@ export default class Example extends Component<
}
}
}
};
});
}

if (this.state.pathMarkerLayer) {
_subLayerProps = Object.assign(_subLayerProps || {}, {
_subLayerProps = Object.assign(_subLayerProps, {
geojson: {
_subLayerProps: {
'line-strings': {
Expand Down
1 change: 1 addition & 0 deletions modules/edit-modes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@turf/transform-scale": ">=4.0.0",
"@turf/transform-translate": ">=4.0.0",
"@turf/union": ">=4.0.0",
"memoizee": "^0.4.14",
"viewport-mercator-project": ">=6.0.0"
}
}
1 change: 1 addition & 0 deletions modules/edit-modes/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export { ImmutableFeatureCollection } from './lib/immutable-feature-collection.j

// Other modes
export { ViewMode } from './lib/view-mode.js';
export { MeasureDistanceMode } from './lib/measure-distance-mode.js';
export { CompositeMode } from './lib/composite-mode.js';
export { SnappableMode } from './lib/snappable-mode.js';

Expand Down
4 changes: 4 additions & 0 deletions modules/edit-modes/src/lib/edit-mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
PointerMoveEvent,
StartDraggingEvent,
StopDraggingEvent,
Tooltip,
ModeProps
} from '../types.js';

Expand All @@ -24,4 +25,7 @@ export interface EditMode<TData, TGuides> {

// Return features that can be used as a guide for editing the data
getGuides(props: ModeProps<TData>): TGuides;

// Return tooltips
getTooltips(props: ModeProps<TData>): Tooltip[];
}
6 changes: 6 additions & 0 deletions modules/edit-modes/src/lib/geojson-edit-mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
StartDraggingEvent,
StopDraggingEvent,
Pick,
Tooltip,
ModeProps
} from '../types.js';
import type {
Expand Down Expand Up @@ -39,6 +40,7 @@ export type EditHandle = {
export type GeoJsonEditAction = EditAction<FeatureCollection>;

const DEFAULT_EDIT_HANDLES: EditHandle[] = [];
const DEFAULT_TOOLTIPS: Tooltip[] = [];

// Main interface for `EditMode`s that edit GeoJSON
export type GeoJsonEditMode = EditMode<FeatureCollection, FeatureCollection>;
Expand Down Expand Up @@ -75,6 +77,10 @@ export class BaseGeoJsonEditMode implements EditMode<FeatureCollection, FeatureC
};
}

getTooltips(props: ModeProps<FeatureCollection>): Tooltip[] {
return DEFAULT_TOOLTIPS;
}

getSelectedFeature(props: ModeProps<FeatureCollection>): ?Feature {
if (props.selectedIndexes.length === 1) {
return props.data.features[props.selectedIndexes[0]];
Expand Down
134 changes: 134 additions & 0 deletions modules/edit-modes/src/lib/measure-distance-mode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// @flow

import turfDistance from '@turf/distance';
import memoize from 'memoizee';
import type {
ClickEvent,
PointerMoveEvent,
StartDraggingEvent,
StopDraggingEvent,
Tooltip,
ModeProps
} from '../types.js';
import type { FeatureCollection, Position } from '../geojson-types.js';
import { BaseGeoJsonEditMode } from './geojson-edit-mode.js';

const DEFAULT_TOOLTIPS = [];

export class MeasureDistanceMode extends BaseGeoJsonEditMode {
startingPoint = null;
endingPoint = null;
endingPointLocked = false;

_setEndingPoint(mapCoords: Position) {
this.endingPoint = {
type: 'Feature',
properties: {
guideType: 'editHandle',
editHandleType: 'existing'
},
geometry: {
type: 'Point',
coordinates: mapCoords
}
};
}

_getTooltips = memoize((modeConfig, startingPoint, endingPoint) => {
let tooltips = DEFAULT_TOOLTIPS;

if (startingPoint && endingPoint) {
const { formatTooltip, turfOptions, measurementCallback } = modeConfig || {};
const units = (turfOptions && turfOptions.units) || 'kilometers';

const distance = turfDistance(startingPoint, endingPoint, turfOptions);

let text;
if (formatTooltip) {
text = formatTooltip(distance);
}
if (!formatTooltip) {
// By default, round to 2 decimal places and append units
text = `${parseFloat(distance).toFixed(2)} ${units}`;
}

if (measurementCallback) {
measurementCallback(distance);
}

tooltips = [
{
position: endingPoint.geometry.coordinates,
text
}
];
}

return tooltips;
});

handleClick(event: ClickEvent, props: ModeProps<FeatureCollection>): void {
if (!this.startingPoint || this.endingPointLocked) {
this.startingPoint = {
type: 'Feature',
properties: {
guideType: 'editHandle',
editHandleType: 'existing'
},
geometry: {
type: 'Point',
coordinates: event.mapCoords
}
};
this.endingPoint = null;
this.endingPointLocked = false;
} else if (this.startingPoint) {
this._setEndingPoint(event.mapCoords);
this.endingPointLocked = true;
}
}

// Called when the pointer moved, regardless of whether the pointer is down, up, and whether something was picked
handlePointerMove(event: PointerMoveEvent, props: ModeProps<FeatureCollection>): void {
if (this.startingPoint && !this.endingPointLocked) {
this._setEndingPoint(event.mapCoords);
}

props.onUpdateCursor('cell');
}

// Called when the pointer went down on something rendered by this layer and the pointer started to move
handleStartDragging(event: StartDraggingEvent, props: ModeProps<FeatureCollection>): void {}

// Called when the pointer went down on something rendered by this layer, the pointer moved, and now the pointer is up
handleStopDragging(event: StopDraggingEvent, props: ModeProps<FeatureCollection>): void {}

// Return features that can be used as a guide for editing the data
getGuides(props: ModeProps<FeatureCollection>): FeatureCollection {
const features = [];
if (this.startingPoint) {
features.push(this.startingPoint);
}
if (this.endingPoint) {
features.push(this.endingPoint);
}
if (this.startingPoint && this.endingPoint) {
features.push({
type: 'Feature',
properties: { guideType: 'tentative' },
geometry: {
type: 'LineString',
coordinates: [
this.startingPoint.geometry.coordinates,
this.endingPoint.geometry.coordinates
]
}
});
}
return { type: 'FeatureCollection', features };
}

getTooltips(props: ModeProps<FeatureCollection>): Tooltip[] {
return this._getTooltips(props.modeConfig, this.startingPoint, this.endingPoint);
}
}
5 changes: 5 additions & 0 deletions modules/edit-modes/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ export type PointerMoveEvent = {
sourceEvent: any
};

export type Tooltip = {
position: Position,
text: string
};

export type ModeProps<TData> = {
// The data being edited, this can be an array or an object
data: TData,
Expand Down
Loading

0 comments on commit 9b60f00

Please sign in to comment.