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

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,28 +43,30 @@ function addInteraction() {
} else if (value === 'Star') {
value = 'Circle';
geometryFunction = function (coordinates, geometry) {
const center = coordinates[0];
const last = coordinates[1];
const dx = center[0] - last[0];
const dy = center[1] - last[1];
const radius = Math.sqrt(dx * dx + dy * dy);
const rotation = Math.atan2(dy, dx);
const newCoordinates = [];
const numPoints = 12;
for (let i = 0; i < numPoints; ++i) {
const angle = rotation + (i * 2 * Math.PI) / numPoints;
const fraction = i % 2 === 0 ? 1 : 0.5;
const offsetX = radius * fraction * Math.cos(angle);
const offsetY = radius * fraction * Math.sin(angle);
newCoordinates.push([center[0] + offsetX, center[1] + offsetY]);
if (coordinates.length) {
const center = coordinates[0];
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);
const rotation = Math.atan2(dy, dx);
const newCoordinates = [];
const numPoints = 12;
for (let i = 0; i < numPoints; ++i) {
const angle = rotation + (i * 2 * Math.PI) / numPoints;
const fraction = i % 2 === 0 ? 1 : 0.5;
const offsetX = radius * fraction * Math.cos(angle);
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({
@@ -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,20 +315,25 @@ class Draw extends PointerInteraction {
* @return {import("../geom/SimpleGeometry.js").default} A geometry.
*/
geometryFunction = function (coordinates, geometry, projection) {
const circle = geometry
? /** @type {Circle} */ (geometry)
: new Circle([NaN, NaN]);
const center = fromUserCoordinate(coordinates[0], projection);
const squaredLength = squaredCoordinateDistance(
center,
fromUserCoordinate(coordinates[1], projection)
);
circle.setCenterAndRadius(center, Math.sqrt(squaredLength));
const userProjection = getUserProjection();
if (userProjection) {
circle.transform(projection, userProjection);
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[coordinates.length - 1],
projection
)
);
circle.setCenterAndRadius(center, Math.sqrt(squaredLength));
const userProjection = getUserProjection();
if (userProjection) {
circle.transform(projection, userProjection);
}
return circle;
}
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)) {
@@ -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.
* @param {import("../MapBrowserEvent.js").default} event Event.
@@ -805,32 +837,13 @@ class Draw extends PointerInteraction {
const sketchPointGeom = this.sketchPoint_.getGeometry();
sketchPointGeom.setCoordinates(coordinate);
}
/** @type {LineString} */
let sketchLineGeom;
if (
geometry.getType() == GeometryType.POLYGON &&
geometry.getType() === GeometryType.POLYGON &&
this.mode_ !== Mode.POLYGON
) {
if (!this.sketchLine_) {
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();
}
this.createOrUpdateCustomSketchLine_(/** @type {Polygon} */ (geometry));
} else if (this.sketchLineCoords_) {
sketchLineGeom = this.sketchLine_.getGeometry();
const sketchLineGeom = this.sketchLine_.getGeometry();
sketchLineGeom.setCoordinates(this.sketchLineCoords_);
}
this.updateSketchFeatures_();
@@ -890,8 +903,6 @@ class Draw extends PointerInteraction {
const geometry = this.sketchFeature_.getGeometry();
const projection = this.getMap().getView().getProjection();
let coordinates;
/** @type {LineString} */
let sketchLineGeom;
if (this.mode_ === Mode.LINE_STRING) {
coordinates = /** @type {LineCoordType} */ (this.sketchCoords_);
coordinates.splice(-2, 1);
@@ -905,11 +916,14 @@ class Draw extends PointerInteraction {
}
}
this.geometryFunction_(coordinates, geometry, projection);
if (geometry.getType() === GeometryType.POLYGON && this.sketchLine_) {
this.createOrUpdateCustomSketchLine_(/** @type {Polygon} */ (geometry));
}
} else if (this.mode_ === Mode.POLYGON) {
coordinates = /** @type {PolyCoordType} */ (this.sketchCoords_)[0];
coordinates.splice(-2, 1);
sketchLineGeom = this.sketchLine_.getGeometry();
if (this.pointerType_ !== 'mouse') {
const sketchLineGeom = this.sketchLine_.getGeometry();
if (coordinates.length >= 2 && this.pointerType_ !== 'mouse') {
const finishCoordinate = coordinates[coordinates.length - 2].slice();
coordinates.pop();
coordinates.push(finishCoordinate);
@@ -940,7 +954,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 +1123,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) {
const center = fromUserCoordinate(
/** @type {LineCoordType} */ (coordinates)[0],
projection
);
const end = fromUserCoordinate(
/** @type {LineCoordType} */ (coordinates)[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) {
const x = end[0] - center[0];
const y = end[1] - center[1];
angle = Math.atan(y / x) - (x < 0 ? Math.PI : 0);
if (coordinates.length) {
const center = fromUserCoordinate(
/** @type {LineCoordType} */ (coordinates)[0],
projection
);
const end = fromUserCoordinate(
/** @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 && opt_angle !== 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() {
return function (coordinates, opt_geometry, projection) {
const extent = boundingExtent(
/** @type {LineCoordType} */ (coordinates).map(function (coordinate) {
return fromUserCoordinate(coordinate, projection);
})
);
const boxCoordinates = [
[
getBottomLeft(extent),
getBottomRight(extent),
getTopRight(extent),
getTopLeft(extent),
getBottomLeft(extent),
],
];
let geometry = opt_geometry;
if (geometry) {
geometry.setCoordinates(boxCoordinates);
} else {
geometry = new Polygon(boxCoordinates);
if (coordinates.length) {
const extent = boundingExtent(
/** @type {LineCoordType} */ ([
coordinates[0],
coordinates[coordinates.length - 1],
]).map(function (coordinate) {
return fromUserCoordinate(coordinate, projection);
})
);
const boxCoordinates = [
[
getBottomLeft(extent),
getBottomRight(extent),
getTopRight(extent),
getTopLeft(extent),
getBottomLeft(extent),
],
];
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;
} 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 +1230,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);