Skip to content

Commit

Permalink
Ability to measure angles with MeasureAngleMode (#332)
Browse files Browse the repository at this point in the history
  • Loading branch information
supersonicclay authored Feb 15, 2020
1 parent 2c8dbec commit 5ada239
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 119 deletions.
16 changes: 16 additions & 0 deletions docs/api-reference/modes/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,22 @@ The following options can be provided in the `modeConfig` object:
* Function to call as measurements are calculated
* Default: `undefined`

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

User can measure an angle by drawing two lines.

### ModeConfig

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

* `formatTooltip` (Function, optional)
* Function to format tooltip text (argument is the numeric area)
* Default: `(distance) => parseFloat(angle).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
4 changes: 3 additions & 1 deletion examples/advanced/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
Draw90DegreePolygonMode,
MeasureDistanceMode,
MeasureAreaMode,
MeasureAngleMode,
ViewMode,
CompositeMode,
SnappableMode,
Expand Down Expand Up @@ -83,7 +84,8 @@ const ALL_MODES = [
modes: [
{ label: 'View', mode: ViewMode },
{ label: 'Measure Distance', mode: MeasureDistanceMode },
{ label: 'Measure Area', mode: MeasureAreaMode }
{ label: 'Measure Area', mode: MeasureAreaMode },
{ label: 'Measure Angle', mode: MeasureAngleMode }
]
},
{
Expand Down
1 change: 0 additions & 1 deletion modules/edit-modes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@
"@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 @@ -29,6 +29,7 @@ export { ImmutableFeatureCollection } from './lib/immutable-feature-collection.j
export { ViewMode } from './lib/view-mode.js';
export { MeasureDistanceMode } from './lib/measure-distance-mode.js';
export { MeasureAreaMode } from './lib/measure-area-mode.js';
export { MeasureAngleMode } from './lib/measure-angle-mode.js';
export { CompositeMode } from './lib/composite-mode.js';
export { SnappableMode } from './lib/snappable-mode.js';

Expand Down
133 changes: 133 additions & 0 deletions modules/edit-modes/src/lib/measure-angle-mode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// @flow

import turfBearing from '@turf/bearing';
import turfCenter from '@turf/center';
import { _memoize } from '@deck.gl/core';

import type {
ClickEvent,
PointerMoveEvent,
Tooltip,
ModeProps,
GuideFeatureCollection
} from '../types.js';
import type { FeatureCollection } from '../geojson-types.js';
import { BaseGeoJsonEditMode } from './geojson-edit-mode.js';

const DEFAULT_TOOLTIPS = [];

export class MeasureAngleMode extends BaseGeoJsonEditMode {
_getTooltips = _memoize(({ modeConfig, vertex, point1, point2 }) => {
let tooltips = DEFAULT_TOOLTIPS;

if (vertex && point1 && point2) {
const { formatTooltip, measurementCallback } = modeConfig || {};
const units = 'deg';

const angle1 = turfBearing(vertex, point1);
const angle2 = turfBearing(vertex, point2);
let angle = Math.abs(angle1 - angle2);
if (angle > 180) {
angle = 360 - angle;
}

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

if (measurementCallback) {
measurementCallback(angle);
}

const position = turfCenter({
type: 'FeatureCollection',
features: [point1, point2].map(p => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: p
}
}))
}).geometry.coordinates;

tooltips = [
{
position,
text
}
];
}

return tooltips;
});

handleClick(event: ClickEvent, props: ModeProps<FeatureCollection>): void {
if (this.getClickSequence().length >= 3) {
this.resetClickSequence();
}

this.addClickSequence(event);
}

// 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 {
props.onUpdateCursor('cell');
}

getPoints(props: ModeProps<FeatureCollection>) {
const clickSequence = this.getClickSequence();

const points = [...clickSequence];

if (clickSequence.length < 3 && props.lastPointerMoveEvent) {
points.push(props.lastPointerMoveEvent.mapCoords);
}

return points;
}

// Return features that can be used as a guide for editing the data
getGuides(props: ModeProps<FeatureCollection>): GuideFeatureCollection {
const guides: GuideFeatureCollection = { type: 'FeatureCollection', features: [] };
const { features } = guides;

const points = this.getPoints(props);

if (points.length > 2) {
features.push({
type: 'Feature',
properties: { guideType: 'tentative' },
geometry: {
type: 'LineString',
coordinates: [points[1], points[0], points[2]]
}
});
} else if (points.length > 1) {
features.push({
type: 'Feature',
properties: { guideType: 'tentative' },
geometry: {
type: 'LineString',
coordinates: [points[1], points[0]]
}
});
}

return guides;
}

getTooltips(props: ModeProps<FeatureCollection>): Tooltip[] {
const points = this.getPoints(props);

return this._getTooltips({
modeConfig: props.modeConfig,
vertex: points[0],
point1: points[1],
point2: points[2]
});
}
}
18 changes: 7 additions & 11 deletions modules/edit-modes/src/lib/measure-distance-mode.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// @flow

import turfDistance from '@turf/distance';
import memoize from 'memoizee';
import { _memoize } from '@deck.gl/core';
import type {
ClickEvent,
PointerMoveEvent,
StartDraggingEvent,
StopDraggingEvent,
Tooltip,
ModeProps,
GuideFeatureCollection,
Expand Down Expand Up @@ -38,7 +36,7 @@ export class MeasureDistanceMode extends BaseGeoJsonEditMode {
};
}

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

if (startingPoint && endingPoint) {
Expand Down Expand Up @@ -102,12 +100,6 @@ export class MeasureDistanceMode extends BaseGeoJsonEditMode {
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>): GuideFeatureCollection {
const guides: GuideFeatureCollection = { type: 'FeatureCollection', features: [] };
Expand Down Expand Up @@ -136,6 +128,10 @@ export class MeasureDistanceMode extends BaseGeoJsonEditMode {
}

getTooltips(props: ModeProps<FeatureCollection>): Tooltip[] {
return this._getTooltips(props.modeConfig, this.startingPoint, this.endingPoint);
return this._getTooltips({
modeConfig: props.modeConfig,
startingPoint: this.startingPoint,
endingPoint: this.endingPoint
});
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 67 additions & 0 deletions modules/edit-modes/test/lib/measure-angle-mode.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// @flow
/* eslint-env jest */

import { MeasureAngleMode } from '../../src/lib/measure-angle-mode.js';
import {
createFeatureCollectionProps,
createClickEvent,
createPointerMoveEvent
} from '../test-utils.js';

describe('move without click', () => {
let mode;
beforeEach(() => {
mode = new MeasureAngleMode();
mode.handlePointerMove(createPointerMoveEvent(), createFeatureCollectionProps());
});

it('tooltips are empty', () => {
const tooltips = mode.getTooltips(createFeatureCollectionProps());
expect(tooltips).toEqual([]);
});
});

describe('one click', () => {
let mode;
beforeEach(() => {
mode = new MeasureAngleMode();
mode.handleClick(createClickEvent([1, 2]), createFeatureCollectionProps());
});

it('tooltips are empty', () => {
const tooltips = mode.getTooltips(createFeatureCollectionProps());
expect(tooltips).toEqual([]);
});
});

describe('three clicks + pointer move', () => {
let mode;
let props;

beforeEach(() => {
mode = new MeasureAngleMode();
props = createFeatureCollectionProps();
mode.handleClick(createClickEvent([1, 1]), props);
mode.handleClick(createClickEvent([-1, 1]), props);
mode.handleClick(createClickEvent([-1, -1]), props);
props.lastPointerMoveEvent = createPointerMoveEvent([1, -1]);
});

it('tooltip contains angle', () => {
const tooltips = mode.getTooltips(props);
expect(tooltips).toMatchSnapshot();
});

it('can measure degrees', () => {
const tooltips = mode.getTooltips(props);
expect(tooltips[0].text).toContain('deg');
});

it('can format angle', () => {
const tooltips = mode.getTooltips({
...props,
modeConfig: { formatTooltip: angle => String(Math.round(angle)) }
});
expect(tooltips[0].text).toEqual('45');
});
});
7 changes: 5 additions & 2 deletions modules/edit-modes/test/lib/measure-area-mode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ describe('three clicks + pointer move', () => {
});

it('can format area', () => {
const tooltips = mode.getTooltips({ ...props, modeConfig: { formatTooltip: String } });
expect(tooltips[0].text).toEqual('49565599608.28442');
const tooltips = mode.getTooltips({
...props,
modeConfig: { formatTooltip: area => String(Math.round(area)) }
});
expect(tooltips[0].text).toEqual('49565599608');
});
});
1 change: 1 addition & 0 deletions modules/main/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,6 @@ export { ImmutableFeatureCollection } from '@nebula.gl/edit-modes';
export { ViewMode } from '@nebula.gl/edit-modes';
export { MeasureDistanceMode } from '@nebula.gl/edit-modes';
export { MeasureAreaMode } from '@nebula.gl/edit-modes';
export { MeasureAngleMode } from '@nebula.gl/edit-modes';
export { CompositeMode } from '@nebula.gl/edit-modes';
export { SnappableMode } from '@nebula.gl/edit-modes';
Loading

0 comments on commit 5ada239

Please sign in to comment.