Merge pull request #11753 from mike-000/patch-15

Use Mode.LINE_STRING in Draw interaction for Circle geometries
This commit is contained in:
Andreas Hocevar
2020-11-25 13:47:17 +01:00
committed by GitHub
4 changed files with 210 additions and 127 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();
+124 -102
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)) {
@@ -735,6 +742,31 @@ class Draw extends PointerInteraction {
} }
} }
/**
* @param {import("../geom/Polygon.js").default} geometry Polygon geometry.
* @private
*/
createOrUpdateCustomSketchLine_(geometry) {
if (!this.sketchLine_) {
this.sketchLine_ = new Feature();
}
const ring = geometry.getLinearRing(0);
let sketchLineGeom = this.sketchLine_.getGeometry();
if (!sketchLineGeom) {
sketchLineGeom = new LineString(
ring.getFlatCoordinates(),
ring.getLayout()
);
this.sketchLine_.setGeometry(sketchLineGeom);
} else {
sketchLineGeom.setFlatCoordinates(
ring.getLayout(),
ring.getFlatCoordinates()
);
sketchLineGeom.changed();
}
}
/** /**
* Start the drawing. * Start the drawing.
* @param {import("../MapBrowserEvent.js").default} event Event. * @param {import("../MapBrowserEvent.js").default} event Event.
@@ -805,32 +837,13 @@ class Draw extends PointerInteraction {
const sketchPointGeom = this.sketchPoint_.getGeometry(); const sketchPointGeom = this.sketchPoint_.getGeometry();
sketchPointGeom.setCoordinates(coordinate); sketchPointGeom.setCoordinates(coordinate);
} }
/** @type {LineString} */
let sketchLineGeom;
if ( if (
geometry.getType() == GeometryType.POLYGON && geometry.getType() === GeometryType.POLYGON &&
this.mode_ !== Mode.POLYGON this.mode_ !== Mode.POLYGON
) { ) {
if (!this.sketchLine_) { this.createOrUpdateCustomSketchLine_(/** @type {Polygon} */ (geometry));
this.sketchLine_ = new Feature();
}
const ring = geometry.getLinearRing(0);
sketchLineGeom = this.sketchLine_.getGeometry();
if (!sketchLineGeom) {
sketchLineGeom = new LineString(
ring.getFlatCoordinates(),
ring.getLayout()
);
this.sketchLine_.setGeometry(sketchLineGeom);
} else {
sketchLineGeom.setFlatCoordinates(
ring.getLayout(),
ring.getFlatCoordinates()
);
sketchLineGeom.changed();
}
} else if (this.sketchLineCoords_) { } else if (this.sketchLineCoords_) {
sketchLineGeom = this.sketchLine_.getGeometry(); const sketchLineGeom = this.sketchLine_.getGeometry();
sketchLineGeom.setCoordinates(this.sketchLineCoords_); sketchLineGeom.setCoordinates(this.sketchLineCoords_);
} }
this.updateSketchFeatures_(); this.updateSketchFeatures_();
@@ -890,8 +903,6 @@ class Draw extends PointerInteraction {
const geometry = this.sketchFeature_.getGeometry(); const geometry = this.sketchFeature_.getGeometry();
const projection = this.getMap().getView().getProjection(); const projection = this.getMap().getView().getProjection();
let coordinates; let coordinates;
/** @type {LineString} */
let sketchLineGeom;
if (this.mode_ === Mode.LINE_STRING) { if (this.mode_ === Mode.LINE_STRING) {
coordinates = /** @type {LineCoordType} */ (this.sketchCoords_); coordinates = /** @type {LineCoordType} */ (this.sketchCoords_);
coordinates.splice(-2, 1); coordinates.splice(-2, 1);
@@ -905,11 +916,14 @@ class Draw extends PointerInteraction {
} }
} }
this.geometryFunction_(coordinates, geometry, projection); this.geometryFunction_(coordinates, geometry, projection);
if (geometry.getType() === GeometryType.POLYGON && this.sketchLine_) {
this.createOrUpdateCustomSketchLine_(/** @type {Polygon} */ (geometry));
}
} 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(); const 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 +954,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 +1123,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 +1176,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 +1221,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 +1230,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);