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
+5 -4
View File
@@ -9,18 +9,19 @@ docs: >
achieved by using `type: 'Circle'` type with a `geometryFunction` that creates 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'` 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 with a `geometryFunction` that creates a box-shaped polygon instead of a
circle. Star drawing uses a custom geometry function that coverts a circle circle. Star drawing uses a custom geometry function that converts a circle
into a start using the center and radius provided by the draw interaction. into a star using the center and radius provided by the draw interaction.
tags: "draw, edit, freehand, vector" tags: "draw, edit, freehand, vector"
--- ---
<div id="map" class="map"></div> <div id="map" class="map"></div>
<form class="form-inline"> <form class="form-inline">
<label for="type">Shape type &nbsp;</label> <label for="type">Shape type: &nbsp;</label>
<select id="type"> <select class="form-control mr-2 mb-2 mt-2" id="type">
<option value="Circle">Circle</option> <option value="Circle">Circle</option>
<option value="Square">Square</option> <option value="Square">Square</option>
<option value="Box">Box</option> <option value="Box">Box</option>
<option value="Star">Star</option> <option value="Star">Star</option>
<option value="None">None</option> <option value="None">None</option>
</select> </select>
<input class="form-control mr-2 mb-2 mt-2" type="button" value="Undo" id="undo">
</form> </form>
+27 -21
View File
@@ -43,28 +43,30 @@ function addInteraction() {
} else if (value === 'Star') { } else if (value === 'Star') {
value = 'Circle'; value = 'Circle';
geometryFunction = function (coordinates, geometry) { geometryFunction = function (coordinates, geometry) {
const center = coordinates[0]; if (coordinates.length) {
const last = coordinates[1]; const center = coordinates[0];
const dx = center[0] - last[0]; const last = coordinates[coordinates.length - 1];
const dy = center[1] - last[1]; const dx = center[0] - last[0];
const radius = Math.sqrt(dx * dx + dy * dy); const dy = center[1] - last[1];
const rotation = Math.atan2(dy, dx); const radius = Math.sqrt(dx * dx + dy * dy);
const newCoordinates = []; const rotation = Math.atan2(dy, dx);
const numPoints = 12; const newCoordinates = [];
for (let i = 0; i < numPoints; ++i) { const numPoints = 12;
const angle = rotation + (i * 2 * Math.PI) / numPoints; for (let i = 0; i < numPoints; ++i) {
const fraction = i % 2 === 0 ? 1 : 0.5; const angle = rotation + (i * 2 * Math.PI) / numPoints;
const offsetX = radius * fraction * Math.cos(angle); const fraction = i % 2 === 0 ? 1 : 0.5;
const offsetY = radius * fraction * Math.sin(angle); const offsetX = radius * fraction * Math.cos(angle);
newCoordinates.push([center[0] + offsetX, center[1] + offsetY]); const offsetY = radius * fraction * Math.sin(angle);
newCoordinates.push([center[0] + offsetX, center[1] + offsetY]);
}
newCoordinates.push(newCoordinates[0].slice());
if (!geometry) {
geometry = new Polygon([newCoordinates]);
} else {
geometry.setCoordinates([newCoordinates]);
}
return geometry;
} }
newCoordinates.push(newCoordinates[0].slice());
if (!geometry) {
geometry = new Polygon([newCoordinates]);
} else {
geometry.setCoordinates([newCoordinates]);
}
return geometry;
}; };
} }
draw = new Draw({ draw = new Draw({
@@ -84,4 +86,8 @@ typeSelect.onchange = function () {
addInteraction(); addInteraction();
}; };
document.getElementById('undo').addEventListener('click', function () {
draw.removeLastPoint();
});
addInteraction(); addInteraction();
+105 -78
View File
@@ -123,7 +123,6 @@ const Mode = {
POINT: 'Point', POINT: 'Point',
LINE_STRING: 'LineString', LINE_STRING: 'LineString',
POLYGON: 'Polygon', POLYGON: 'Polygon',
CIRCLE: 'Circle',
}; };
/** /**
@@ -290,7 +289,12 @@ class Draw extends PointerInteraction {
* @type {number} * @type {number}
* @private * @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 * A function to decide if a potential finish coordinate is permissible
@@ -311,20 +315,25 @@ class Draw extends PointerInteraction {
* @return {import("../geom/SimpleGeometry.js").default} A geometry. * @return {import("../geom/SimpleGeometry.js").default} A geometry.
*/ */
geometryFunction = function (coordinates, geometry, projection) { geometryFunction = function (coordinates, geometry, projection) {
const circle = geometry if (coordinates.length) {
? /** @type {Circle} */ (geometry) const circle = geometry
: new Circle([NaN, NaN]); ? /** @type {Circle} */ (geometry)
const center = fromUserCoordinate(coordinates[0], projection); : new Circle([NaN, NaN]);
const squaredLength = squaredCoordinateDistance( const center = fromUserCoordinate(coordinates[0], projection);
center, const squaredLength = squaredCoordinateDistance(
fromUserCoordinate(coordinates[1], projection) center,
); fromUserCoordinate(
circle.setCenterAndRadius(center, Math.sqrt(squaredLength)); coordinates[coordinates.length - 1],
const userProjection = getUserProjection(); projection
if (userProjection) { )
circle.transform(projection, userProjection); );
circle.setCenterAndRadius(center, Math.sqrt(squaredLength));
const userProjection = getUserProjection();
if (userProjection) {
circle.transform(projection, userProjection);
}
return circle;
} }
return circle;
}; };
} else { } else {
let Constructor; let Constructor;
@@ -551,7 +560,7 @@ class Draw extends PointerInteraction {
event.preventDefault(); event.preventDefault();
} }
} else if ( } else if (
event.originalEvent.pointerType == 'mouse' || event.originalEvent.pointerType === 'mouse' ||
(event.type === MapBrowserEventType.POINTERDRAG && (event.type === MapBrowserEventType.POINTERDRAG &&
this.downTimeout_ === undefined) this.downTimeout_ === undefined)
) { ) {
@@ -618,15 +627,13 @@ class Draw extends PointerInteraction {
this.handlePointerMove_(event); this.handlePointerMove_(event);
const circleMode = this.mode_ === Mode.CIRCLE;
if (this.shouldHandle_) { if (this.shouldHandle_) {
if (!this.finishCoordinate_) { if (!this.finishCoordinate_) {
this.startDrawing_(event); this.startDrawing_(event);
if (this.mode_ === Mode.POINT) { if (this.mode_ === Mode.POINT) {
this.finishDrawing(); this.finishDrawing();
} }
} else if (this.freehand_ || circleMode) { } else if (this.freehand_) {
this.finishDrawing(); this.finishDrawing();
} else if (this.atFinish_(event)) { } else if (this.atFinish_(event)) {
if (this.finishCondition_(event)) { if (this.finishCondition_(event)) {
@@ -808,7 +815,7 @@ class Draw extends PointerInteraction {
/** @type {LineString} */ /** @type {LineString} */
let sketchLineGeom; let sketchLineGeom;
if ( if (
geometry.getType() == GeometryType.POLYGON && geometry.getType() === GeometryType.POLYGON &&
this.mode_ !== Mode.POLYGON this.mode_ !== Mode.POLYGON
) { ) {
if (!this.sketchLine_) { if (!this.sketchLine_) {
@@ -905,11 +912,23 @@ class Draw extends PointerInteraction {
} }
} }
this.geometryFunction_(coordinates, geometry, projection); 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) { } else if (this.mode_ === Mode.POLYGON) {
coordinates = /** @type {PolyCoordType} */ (this.sketchCoords_)[0]; coordinates = /** @type {PolyCoordType} */ (this.sketchCoords_)[0];
coordinates.splice(-2, 1); coordinates.splice(-2, 1);
sketchLineGeom = this.sketchLine_.getGeometry(); sketchLineGeom = this.sketchLine_.getGeometry();
if (this.pointerType_ !== 'mouse') { if (coordinates.length >= 2 && this.pointerType_ !== 'mouse') {
const finishCoordinate = coordinates[coordinates.length - 2].slice(); const finishCoordinate = coordinates[coordinates.length - 2].slice();
coordinates.pop(); coordinates.pop();
coordinates.push(finishCoordinate); coordinates.push(finishCoordinate);
@@ -940,7 +959,7 @@ class Draw extends PointerInteraction {
let coordinates = this.sketchCoords_; let coordinates = this.sketchCoords_;
const geometry = sketchFeature.getGeometry(); const geometry = sketchFeature.getGeometry();
const projection = this.getMap().getView().getProjection(); 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 // remove the redundant last point
coordinates.pop(); coordinates.pop();
this.geometryFunction_(coordinates, geometry, projection); this.geometryFunction_(coordinates, geometry, projection);
@@ -1109,43 +1128,47 @@ function getDefaultStyleFunction() {
/** /**
* Create a `geometryFunction` for `type: 'Circle'` that will create a regular * 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. * `import("../geom/Circle.js").Circle` geometry.
* @param {number=} opt_sides Number of sides of the regular polygon. Default is * @param {number=} opt_sides Number of sides of the regular polygon.
* 32. * Default is 32.
* @param {number=} opt_angle Angle of the first point in radians. 0 means East. * @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 * Default is the angle defined by the heading from the center of the
* regular polygon to the current pointer position. * regular polygon to the current pointer position.
* @return {GeometryFunction} Function that draws a * @return {GeometryFunction} Function that draws a polygon.
* polygon.
* @api * @api
*/ */
export function createRegularPolygon(opt_sides, opt_angle) { export function createRegularPolygon(opt_sides, opt_angle) {
return function (coordinates, opt_geometry, projection) { return function (coordinates, opt_geometry, projection) {
const center = fromUserCoordinate( if (coordinates.length) {
/** @type {LineCoordType} */ (coordinates)[0], const center = fromUserCoordinate(
projection /** @type {LineCoordType} */ (coordinates)[0],
); projection
const end = fromUserCoordinate( );
/** @type {LineCoordType} */ (coordinates)[1], const end = fromUserCoordinate(
projection /** @type {LineCoordType} */ (coordinates)[coordinates.length - 1],
); projection
const radius = Math.sqrt(squaredCoordinateDistance(center, end)); );
const geometry = opt_geometry const radius = Math.sqrt(squaredCoordinateDistance(center, end));
? /** @type {Polygon} */ (opt_geometry) const geometry = opt_geometry
: fromCircle(new Circle(center), opt_sides); ? /** @type {Polygon} */ (opt_geometry)
let angle = opt_angle; : fromCircle(new Circle(center), opt_sides);
if (!opt_angle) {
const x = end[0] - center[0]; let angle = opt_angle;
const y = end[1] - center[1]; if (!opt_angle && opt_angle !== 0) {
angle = Math.atan(y / x) - (x < 0 ? Math.PI : 0); const x = end[0] - center[0];
const y = end[1] - center[1];
angle = Math.atan2(y, x);
}
makeRegular(geometry, center, radius, angle);
const userProjection = getUserProjection();
if (userProjection) {
geometry.transform(projection, userProjection);
}
return geometry;
} }
makeRegular(geometry, center, radius, angle);
const userProjection = getUserProjection();
if (userProjection) {
geometry.transform(projection, userProjection);
}
return geometry;
}; };
} }
@@ -1158,31 +1181,36 @@ export function createRegularPolygon(opt_sides, opt_angle) {
*/ */
export function createBox() { export function createBox() {
return function (coordinates, opt_geometry, projection) { return function (coordinates, opt_geometry, projection) {
const extent = boundingExtent( if (coordinates.length) {
/** @type {LineCoordType} */ (coordinates).map(function (coordinate) { const extent = boundingExtent(
return fromUserCoordinate(coordinate, projection); /** @type {LineCoordType} */ ([
}) coordinates[0],
); coordinates[coordinates.length - 1],
const boxCoordinates = [ ]).map(function (coordinate) {
[ return fromUserCoordinate(coordinate, projection);
getBottomLeft(extent), })
getBottomRight(extent), );
getTopRight(extent), const boxCoordinates = [
getTopLeft(extent), [
getBottomLeft(extent), getBottomLeft(extent),
], getBottomRight(extent),
]; getTopRight(extent),
let geometry = opt_geometry; getTopLeft(extent),
if (geometry) { getBottomLeft(extent),
geometry.setCoordinates(boxCoordinates); ],
} else { ];
geometry = new Polygon(boxCoordinates); let geometry = opt_geometry;
if (geometry) {
geometry.setCoordinates(boxCoordinates);
} else {
geometry = new Polygon(boxCoordinates);
}
const userProjection = getUserProjection();
if (userProjection) {
geometry.transform(projection, userProjection);
}
return geometry;
} }
const userProjection = getUserProjection();
if (userProjection) {
geometry.transform(projection, userProjection);
}
return geometry;
}; };
} }
@@ -1198,7 +1226,8 @@ function getMode(type) {
mode = Mode.POINT; mode = Mode.POINT;
} else if ( } else if (
type === GeometryType.LINE_STRING || type === GeometryType.LINE_STRING ||
type === GeometryType.MULTI_LINE_STRING type === GeometryType.MULTI_LINE_STRING ||
type === GeometryType.CIRCLE
) { ) {
mode = Mode.LINE_STRING; mode = Mode.LINE_STRING;
} else if ( } else if (
@@ -1206,8 +1235,6 @@ function getMode(type) {
type === GeometryType.MULTI_POLYGON type === GeometryType.MULTI_POLYGON
) { ) {
mode = Mode.POLYGON; mode = Mode.POLYGON;
} else if (type === GeometryType.CIRCLE) {
mode = Mode.CIRCLE;
} }
return /** @type {!Mode} */ (mode); return /** @type {!Mode} */ (mode);
} }
+54
View File
@@ -1346,6 +1346,33 @@ describe('ol.interaction.Draw', function () {
describe('createRegularPolygon', function () { describe('createRegularPolygon', function () {
it('creates a regular polygon in Circle mode', 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({ const draw = new Draw({
source: source, source: source,
type: 'Circle', type: 'Circle',
@@ -1372,6 +1399,33 @@ describe('ol.interaction.Draw', function () {
expect(coordinates[0][0][1]).to.roughlyEqual(20, 1e-9); 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 () { it('creates a regular polygon in Circle mode in a user projection', function () {
const userProjection = 'EPSG:3857'; const userProjection = 'EPSG:3857';
setUserProjection(userProjection); setUserProjection(userProjection);