Merge pull request #11753 from mike-000/patch-15
Use Mode.LINE_STRING in Draw interaction for Circle geometries
This commit is contained in:
@@ -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 </label>
|
||||
<select id="type">
|
||||
<label for="type">Shape type: </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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user