Use Mode.LINE_STRING for Circle geometries

Make predefined geometry functions compatible with removeLastPoint
Add undo to example
Make geometry function for Star compatible with use of removeLastPoint
Additional createRegularPolygon tests
This commit is contained in:
mike-000
2020-11-18 13:16:05 +00:00
parent e44f23f806
commit f8df533cb0
4 changed files with 191 additions and 103 deletions

View File

@@ -9,18 +9,19 @@ docs: >
achieved by using `type: 'Circle'` type with a `geometryFunction` that creates
a 4-sided regular polygon instead of a circle. Box drawing uses `type: 'Circle'`
with a `geometryFunction` that creates a box-shaped polygon instead of a
circle. Star drawing uses a custom geometry function that coverts a circle
into a start using the center and radius provided by the draw interaction.
circle. Star drawing uses a custom geometry function that converts a circle
into a star using the center and radius provided by the draw interaction.
tags: "draw, edit, freehand, vector"
---
<div id="map" class="map"></div>
<form class="form-inline">
<label for="type">Shape type &nbsp;</label>
<select id="type">
<label for="type">Shape type: &nbsp;</label>
<select class="form-control mr-2 mb-2 mt-2" id="type">
<option value="Circle">Circle</option>
<option value="Square">Square</option>
<option value="Box">Box</option>
<option value="Star">Star</option>
<option value="None">None</option>
</select>
<input class="form-control mr-2 mb-2 mt-2" type="button" value="Undo" id="undo">
</form>

View File

@@ -43,8 +43,9 @@ function addInteraction() {
} else if (value === 'Star') {
value = 'Circle';
geometryFunction = function (coordinates, geometry) {
if (coordinates.length) {
const center = coordinates[0];
const last = coordinates[1];
const last = coordinates[coordinates.length - 1];
const dx = center[0] - last[0];
const dy = center[1] - last[1];
const radius = Math.sqrt(dx * dx + dy * dy);
@@ -65,6 +66,7 @@ function addInteraction() {
geometry.setCoordinates([newCoordinates]);
}
return geometry;
}
};
}
draw = new Draw({
@@ -84,4 +86,8 @@ typeSelect.onchange = function () {
addInteraction();
};
document.getElementById('undo').addEventListener('click', function () {
draw.removeLastPoint();
});
addInteraction();

View File

@@ -123,7 +123,6 @@ const Mode = {
POINT: 'Point',
LINE_STRING: 'LineString',
POLYGON: 'Polygon',
CIRCLE: 'Circle',
};
/**
@@ -290,7 +289,12 @@ class Draw extends PointerInteraction {
* @type {number}
* @private
*/
this.maxPoints_ = options.maxPoints ? options.maxPoints : Infinity;
this.maxPoints_ =
this.type_ === GeometryType.CIRCLE
? 2
: options.maxPoints
? options.maxPoints
: Infinity;
/**
* A function to decide if a potential finish coordinate is permissible
@@ -311,13 +315,17 @@ class Draw extends PointerInteraction {
* @return {import("../geom/SimpleGeometry.js").default} A geometry.
*/
geometryFunction = function (coordinates, geometry, projection) {
if (coordinates.length) {
const circle = geometry
? /** @type {Circle} */ (geometry)
: new Circle([NaN, NaN]);
const center = fromUserCoordinate(coordinates[0], projection);
const squaredLength = squaredCoordinateDistance(
center,
fromUserCoordinate(coordinates[1], projection)
fromUserCoordinate(
coordinates[coordinates.length - 1],
projection
)
);
circle.setCenterAndRadius(center, Math.sqrt(squaredLength));
const userProjection = getUserProjection();
@@ -325,6 +333,7 @@ class Draw extends PointerInteraction {
circle.transform(projection, userProjection);
}
return circle;
}
};
} else {
let Constructor;
@@ -551,7 +560,7 @@ class Draw extends PointerInteraction {
event.preventDefault();
}
} else if (
event.originalEvent.pointerType == 'mouse' ||
event.originalEvent.pointerType === 'mouse' ||
(event.type === MapBrowserEventType.POINTERDRAG &&
this.downTimeout_ === undefined)
) {
@@ -618,15 +627,13 @@ class Draw extends PointerInteraction {
this.handlePointerMove_(event);
const circleMode = this.mode_ === Mode.CIRCLE;
if (this.shouldHandle_) {
if (!this.finishCoordinate_) {
this.startDrawing_(event);
if (this.mode_ === Mode.POINT) {
this.finishDrawing();
}
} else if (this.freehand_ || circleMode) {
} else if (this.freehand_) {
this.finishDrawing();
} else if (this.atFinish_(event)) {
if (this.finishCondition_(event)) {
@@ -808,7 +815,7 @@ class Draw extends PointerInteraction {
/** @type {LineString} */
let sketchLineGeom;
if (
geometry.getType() == GeometryType.POLYGON &&
geometry.getType() === GeometryType.POLYGON &&
this.mode_ !== Mode.POLYGON
) {
if (!this.sketchLine_) {
@@ -905,11 +912,23 @@ class Draw extends PointerInteraction {
}
}
this.geometryFunction_(coordinates, geometry, projection);
if (geometry.getType() === GeometryType.POLYGON && this.sketchLine_) {
sketchLineGeom = this.sketchLine_.getGeometry();
if (sketchLineGeom) {
const ring = geometry.getLinearRing(0);
sketchLineGeom.setFlatCoordinates(
ring.getLayout(),
ring.getFlatCoordinates()
);
sketchLineGeom.changed();
}
}
} else if (this.mode_ === Mode.POLYGON) {
coordinates = /** @type {PolyCoordType} */ (this.sketchCoords_)[0];
coordinates.splice(-2, 1);
sketchLineGeom = this.sketchLine_.getGeometry();
if (this.pointerType_ !== 'mouse') {
if (coordinates.length >= 2 && this.pointerType_ !== 'mouse') {
const finishCoordinate = coordinates[coordinates.length - 2].slice();
coordinates.pop();
coordinates.push(finishCoordinate);
@@ -940,7 +959,7 @@ class Draw extends PointerInteraction {
let coordinates = this.sketchCoords_;
const geometry = sketchFeature.getGeometry();
const projection = this.getMap().getView().getProjection();
if (this.mode_ === Mode.LINE_STRING) {
if (this.mode_ === Mode.LINE_STRING && this.type_ !== GeometryType.CIRCLE) {
// remove the redundant last point
coordinates.pop();
this.geometryFunction_(coordinates, geometry, projection);
@@ -1109,43 +1128,47 @@ function getDefaultStyleFunction() {
/**
* Create a `geometryFunction` for `type: 'Circle'` that will create a regular
* polygon with a user specified number of sides and start angle instead of an
* polygon with a user specified number of sides and start angle instead of a
* `import("../geom/Circle.js").Circle` geometry.
* @param {number=} opt_sides Number of sides of the regular polygon. Default is
* 32.
* @param {number=} opt_angle Angle of the first point in radians. 0 means East.
* @param {number=} opt_sides Number of sides of the regular polygon.
* Default is 32.
* @param {number=} opt_angle Angle of the first point in counter-clockwise
* radians. 0 means East.
* Default is the angle defined by the heading from the center of the
* regular polygon to the current pointer position.
* @return {GeometryFunction} Function that draws a
* polygon.
* @return {GeometryFunction} Function that draws a polygon.
* @api
*/
export function createRegularPolygon(opt_sides, opt_angle) {
return function (coordinates, opt_geometry, projection) {
if (coordinates.length) {
const center = fromUserCoordinate(
/** @type {LineCoordType} */ (coordinates)[0],
projection
);
const end = fromUserCoordinate(
/** @type {LineCoordType} */ (coordinates)[1],
/** @type {LineCoordType} */ (coordinates)[coordinates.length - 1],
projection
);
const radius = Math.sqrt(squaredCoordinateDistance(center, end));
const geometry = opt_geometry
? /** @type {Polygon} */ (opt_geometry)
: fromCircle(new Circle(center), opt_sides);
let angle = opt_angle;
if (!opt_angle) {
if (!opt_angle && opt_angle !== 0) {
const x = end[0] - center[0];
const y = end[1] - center[1];
angle = Math.atan(y / x) - (x < 0 ? Math.PI : 0);
angle = Math.atan2(y, x);
}
makeRegular(geometry, center, radius, angle);
const userProjection = getUserProjection();
if (userProjection) {
geometry.transform(projection, userProjection);
}
return geometry;
}
};
}
@@ -1158,8 +1181,12 @@ export function createRegularPolygon(opt_sides, opt_angle) {
*/
export function createBox() {
return function (coordinates, opt_geometry, projection) {
if (coordinates.length) {
const extent = boundingExtent(
/** @type {LineCoordType} */ (coordinates).map(function (coordinate) {
/** @type {LineCoordType} */ ([
coordinates[0],
coordinates[coordinates.length - 1],
]).map(function (coordinate) {
return fromUserCoordinate(coordinate, projection);
})
);
@@ -1183,6 +1210,7 @@ export function createBox() {
geometry.transform(projection, userProjection);
}
return geometry;
}
};
}
@@ -1198,7 +1226,8 @@ function getMode(type) {
mode = Mode.POINT;
} else if (
type === GeometryType.LINE_STRING ||
type === GeometryType.MULTI_LINE_STRING
type === GeometryType.MULTI_LINE_STRING ||
type === GeometryType.CIRCLE
) {
mode = Mode.LINE_STRING;
} else if (
@@ -1206,8 +1235,6 @@ function getMode(type) {
type === GeometryType.MULTI_POLYGON
) {
mode = Mode.POLYGON;
} else if (type === GeometryType.CIRCLE) {
mode = Mode.CIRCLE;
}
return /** @type {!Mode} */ (mode);
}

View File

@@ -1346,6 +1346,33 @@ describe('ol.interaction.Draw', function () {
describe('createRegularPolygon', function () {
it('creates a regular polygon in Circle mode', function () {
const draw = new Draw({
source: source,
type: 'Circle',
geometryFunction: createRegularPolygon(4),
});
map.addInteraction(draw);
// first point
simulateEvent('pointermove', 0, 0);
simulateEvent('pointerdown', 0, 0);
simulateEvent('pointerup', 0, 0);
// finish on second point
simulateEvent('pointermove', 20, 20);
simulateEvent('pointerdown', 20, 20);
simulateEvent('pointerup', 20, 20);
const features = source.getFeatures();
const geometry = features[0].getGeometry();
expect(geometry).to.be.a(Polygon);
const coordinates = geometry.getCoordinates();
expect(coordinates[0].length).to.eql(5);
expect(coordinates[0][0][0]).to.roughlyEqual(20, 1e-9);
expect(coordinates[0][0][1]).to.roughlyEqual(-20, 1e-9);
});
it('creates a regular polygon at specified angle', function () {
const draw = new Draw({
source: source,
type: 'Circle',
@@ -1372,6 +1399,33 @@ describe('ol.interaction.Draw', function () {
expect(coordinates[0][0][1]).to.roughlyEqual(20, 1e-9);
});
it('creates a regular polygon at specified 0 angle', function () {
const draw = new Draw({
source: source,
type: 'Circle',
geometryFunction: createRegularPolygon(4, 0),
});
map.addInteraction(draw);
// first point
simulateEvent('pointermove', 0, 0);
simulateEvent('pointerdown', 0, 0);
simulateEvent('pointerup', 0, 0);
// finish on second point
simulateEvent('pointermove', 20, 20);
simulateEvent('pointerdown', 20, 20);
simulateEvent('pointerup', 20, 20);
const features = source.getFeatures();
const geometry = features[0].getGeometry();
expect(geometry).to.be.a(Polygon);
const coordinates = geometry.getCoordinates();
expect(coordinates[0].length).to.eql(5);
expect(coordinates[0][0][0]).to.roughlyEqual(28.2842712474619, 1e-9);
expect(coordinates[0][0][1]).to.roughlyEqual(0, 1e-9);
});
it('creates a regular polygon in Circle mode in a user projection', function () {
const userProjection = 'EPSG:3857';
setUserProjection(userProjection);