Merge pull request #7703 from ahocevar/draw-state

Improved drawing experience on touch devices
This commit is contained in:
Andreas Hocevar
2018-01-17 20:25:07 +01:00
parent 6e9fc2cbad
commit f7a1aba38e
6 changed files with 221 additions and 12 deletions

View File

@@ -1,5 +1,103 @@
## Upgrade notes
### Next release
#### Changed behavior of the `Draw` interaction
For better drawing experience, two changes were made to the behavior of the Draw interaction:
1. On long press, the current vertex can be dragged to its desired position.
2. On touch move (e.g. when panning the map on a mobile device), no draw cursor is shown, and the geometry being drawn is not updated. But because of 1., the draw cursor will appear on long press. Mouse moves are not affected by this change.
#### Changes in proj4 integration
Because relying on a globally available proj4 is not practical with ES modules, we have made a change to the way we integrate proj4:
* The `setProj4()` function from the `ol/proj` module was removed.
* A new `ol/proj/proj4` module with a `register()` function was added. Regardless of whether the application imports `proj4` or uses a global `proj4`, this function needs to be called with the proj4 instance as argument whenever projection definitions were added to proj4's registry with (`proj4.defs`).
It is also recommended to no longer use a global `proj4`. Instead,
npm install proj4
and import it:
```js
import proj4 from 'proj4';
```
Applications can be updated by importing the `register` function from the `ol/proj/proj4` module
```js
import {register} from 'ol/proj/proj4'
```
and calling it before using projections, and any time the proj4 registry was changed by `proj4.defs()` calls:
```js
register(proj4);
```
#### Removal of logos
The map and sources no longer accept a `logo` option. Instead, if you wish to append a logo to your map, add the desired markup directly in your HTML. In addition, you can use the `attributions` property of a source to display arbitrary markup per-source with the attribution control.
#### Replacement of `ol/Sphere` constructor with `ol/sphere` functions
The `ol/Sphere` constructor has been removed. If you were using the `getGeodesicArea` method, use the `getArea` function instead. If you were using the `haversineDistance` method, use the `getDistance` function instead.
Examples before:
```js
// using ol@4
import Sphere from 'ol/sphere';
var sphere = new Sphere(Sphere.DEFAULT_RADIUS);
var area = sphere.getGeodesicArea(polygon);
var distance = sphere.haversineDistance(g1, g2);
```
Examples after:
```js
// using ol@5
import {circular as circularPolygon} from 'ol/geom/Polygon';
import {getArea, getDistance} from 'ol/sphere';
var area = getArea(polygon);
var distance = getDistance(g1, g2);
var circle = circularPolygon(center, radius);
```
#### New signature for the `circular` function for creating polygons
The `circular` function exported from `ol/geom/Polygon` no longer requires a `Sphere` as the first argument.
Example before:
```js
// using ol@4
import Polygon from 'ol/geom/polygon';
import Sphere from 'ol/sphere';
var poly = Polygon.circular(new Sphere(Sphere.DEFAULT_RADIUS), center, radius);
```
Example after:
```js
// using ol@5
import {circular as circularPolygon} from 'ol/geom/Polygon';
var poly = circularPolygon(center, radius);
```
#### Removal of optional this arguments.
The following methods did get the optional this (i.e. opt_this) arguments removed. Please use closures, the es6 arrow function or the bind method to achieve this effect (Bind is explained here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
* Collection#forEach
* geom/LineString#forEachSegment
* Observable#on, #once, #un
* source/TileUTFGrid#forDataAtCoordinateAndResolution
* source/Vector#forEachFeature, #forEachFeatureInExtent, #forEachFeatureIntersectingExtent
### v4.6.0
#### Renamed `exceedLength` option of `ol.style.Text` to `overflow`

View File

@@ -3003,6 +3003,7 @@ olx.interaction.DragZoomOptions.prototype.out;
* @typedef {{clickTolerance: (number|undefined),
* features: (ol.Collection.<ol.Feature>|undefined),
* source: (ol.source.Vector|undefined),
* dragVertexDelay: (number|undefined),
* snapTolerance: (number|undefined),
* type: (ol.geom.GeometryType|string),
* stopClick: (boolean|undefined),
@@ -3048,6 +3049,14 @@ olx.interaction.DrawOptions.prototype.features;
olx.interaction.DrawOptions.prototype.source;
/**
* Delay in milliseconds after pointerdown before the current vertex can be
* dragged to its exact position. Default is 500 ms.
* @type {number|undefined}
*/
olx.interaction.DrawOptions.prototype.dragVertexDelay;
/**
* Pixel distance for snapping to the drawing finish. Default is `12`.
* @type {number|undefined}

View File

@@ -13,6 +13,7 @@ ol.events.EventType = {
CHANGE: 'change',
CLEAR: 'clear',
CONTEXTMENU: 'contextmenu',
CLICK: 'click',
DBLCLICK: 'dblclick',
DRAGENTER: 'dragenter',

View File

@@ -1,6 +1,7 @@
goog.provide('ol.interaction.Draw');
goog.require('ol');
goog.require('ol.events.EventType');
goog.require('ol.Feature');
goog.require('ol.MapBrowserEventType');
goog.require('ol.Object');
@@ -22,6 +23,7 @@ goog.require('ol.interaction.DrawEventType');
goog.require('ol.interaction.Pointer');
goog.require('ol.interaction.Property');
goog.require('ol.layer.Vector');
goog.require('ol.pointer.MouseSource');
goog.require('ol.source.Vector');
goog.require('ol.style.Style');
@@ -56,6 +58,18 @@ ol.interaction.Draw = function(options) {
*/
this.downPx_ = null;
/**
* @type {number}
* @private
*/
this.downTimeout_;
/**
* @type {number}
* @private
*/
this.lastDragTime_;
/**
* @type {boolean}
* @private
@@ -191,6 +205,12 @@ ol.interaction.Draw = function(options) {
*/
this.geometryFunction_ = geometryFunction;
/**
* @type {number}
* @private
*/
this.dragVertexDelay_ = options.dragVertexDelay !== undefined ? options.dragVertexDelay : 500;
/**
* Finish coordinate for the feature (first point for polygons, last point for
* linestrings).
@@ -255,7 +275,8 @@ ol.interaction.Draw = function(options) {
wrapX: options.wrapX ? options.wrapX : false
}),
style: options.style ? options.style :
ol.interaction.Draw.getDefaultStyleFunction()
ol.interaction.Draw.getDefaultStyleFunction(),
updateWhileAnimating: true
});
/**
@@ -321,8 +342,27 @@ ol.interaction.Draw.prototype.setMap = function(map) {
* @api
*/
ol.interaction.Draw.handleEvent = function(event) {
if (event.originalEvent.type === ol.events.EventType.CONTEXTMENU) {
// Avoid context menu for long taps when drawing on mobile
event.preventDefault();
}
this.freehand_ = this.mode_ !== ol.interaction.Draw.Mode_.POINT && this.freehandCondition_(event);
var pass = true;
let move = event.type === ol.MapBrowserEventType.POINTERMOVE;
let pass = true;
if (this.lastDragTime_ && event.type === ol.MapBrowserEventType.POINTERDRAG) {
const now = Date.now();
if (now - this.lastDragTime_ >= this.dragVertexDelay_) {
this.downPx_ = event.pixel;
this.shouldHandle_ = !this.freehand_;
move = true;
} else {
this.lastDragTime_ = undefined;
}
if (this.shouldHandle_ && this.downTimeout_) {
clearTimeout(this.downTimeout_);
this.downTimeout_ = undefined;
}
}
if (this.freehand_ &&
event.type === ol.MapBrowserEventType.POINTERDRAG &&
this.sketchFeature_ !== null) {
@@ -331,11 +371,18 @@ ol.interaction.Draw.handleEvent = function(event) {
} else if (this.freehand_ &&
event.type === ol.MapBrowserEventType.POINTERDOWN) {
pass = false;
} else if (event.type === ol.MapBrowserEventType.POINTERMOVE) {
pass = this.handlePointerMove_(event);
} else if (move) {
pass = event.type === ol.MapBrowserEventType.POINTERMOVE;
if (pass && this.freehand_) {
pass = this.handlePointerMove_(event);
} else if (event.pointerEvent.pointerType == ol.pointer.MouseSource.POINTER_TYPE ||
(event.type === ol.MapBrowserEventType.POINTERDRAG && !this.downTimeout_)) {
this.handlePointerMove_(event);
}
} else if (event.type === ol.MapBrowserEventType.DBLCLICK) {
pass = false;
}
return ol.interaction.Pointer.handleEvent.call(this, event) && pass;
};
@@ -356,6 +403,11 @@ ol.interaction.Draw.handleDownEvent_ = function(event) {
}
return true;
} else if (this.condition_(event)) {
this.lastDragTime_ = Date.now();
this.downTimeout_ = setTimeout(function() {
this.handlePointerMove_(new MapBrowserPointerEvent(
MapBrowserEventType.POINTERMOVE, event.map, event.pointerEvent, event.frameState));
}.bind(this), this.dragVertexDelay_);
this.downPx_ = event.pixel;
return true;
} else {
@@ -373,6 +425,11 @@ ol.interaction.Draw.handleDownEvent_ = function(event) {
ol.interaction.Draw.handleUpEvent_ = function(event) {
var pass = true;
if (this.downTimeout_) {
clearTimeout(this.downTimeout_);
this.downTimeout_ = undefined;
}
this.handlePointerMove_(event);
var circleMode = this.mode_ === ol.interaction.Draw.Mode_.CIRCLE;
@@ -422,6 +479,9 @@ ol.interaction.Draw.prototype.handlePointerMove_ = function(event) {
this.shouldHandle_ = this.freehand_ ?
squaredDistance > this.squaredClickTolerance_ :
squaredDistance <= this.squaredClickTolerance_;
if (!this.shouldHandle_) {
return true;
}
}
if (this.finishCoordinate_) {
@@ -504,9 +564,6 @@ ol.interaction.Draw.prototype.startDrawing_ = function(event) {
this.sketchLineCoords_ = this.sketchCoords_[0];
} else {
this.sketchCoords_ = [start.slice(), start.slice()];
if (this.mode_ === ol.interaction.Draw.Mode_.CIRCLE) {
this.sketchLineCoords_ = this.sketchCoords_;
}
}
if (this.sketchLineCoords_) {
this.sketchLine_ = new ol.Feature(

View File

@@ -205,9 +205,11 @@ ol.PluggableMap = function(options) {
*/
this.keyHandlerKeys_ = null;
ol.events.listen(this.viewport_, ol.events.EventType.WHEEL,
ol.events.listen(this.viewport_, EventType.CONTEXTMENU,
this.handleBrowserEvent, this);
ol.events.listen(this.viewport_, ol.events.EventType.MOUSEWHEEL,
ol.events.listen(this.viewport_, EventType.WHEEL,
this.handleBrowserEvent, this);
ol.events.listen(this.viewport_, EventType.MOUSEWHEEL,
this.handleBrowserEvent, this);
/**
@@ -428,9 +430,11 @@ ol.PluggableMap.prototype.addOverlayInternal_ = function(overlay) {
*/
ol.PluggableMap.prototype.disposeInternal = function() {
this.mapBrowserEventHandler_.dispose();
ol.events.unlisten(this.viewport_, ol.events.EventType.WHEEL,
ol.events.unlisten(this.viewport_, EventType.CONTEXTMENU,
this.handleBrowserEvent, this);
ol.events.unlisten(this.viewport_, ol.events.EventType.MOUSEWHEEL,
ol.events.unlisten(this.viewport_, EventType.WHEEL,
this.handleBrowserEvent, this);
ol.events.unlisten(this.viewport_, EventType.MOUSEWHEEL,
this.handleBrowserEvent, this);
if (this.handleResize_ !== undefined) {
window.removeEventListener(ol.events.EventType.RESIZE,

View File

@@ -105,6 +105,15 @@ describe('ol.interaction.Draw', function() {
expect(draw.freehandCondition_(event)).to.be(true);
});
it('accepts a dragVertexDelay option', function() {
const draw = new Draw({
source: source,
type: 'LineString',
dragVertexDelay: 42
});
expect(draw.dragVertexDelay_).to.be(42);
});
});
describe('specifying a geometryName', function() {
@@ -341,7 +350,6 @@ describe('ol.interaction.Draw', function() {
simulateEvent('pointermove', 20, 30);
// freehand
simulateEvent('pointerdown', 20, 30, true);
simulateEvent('pointermove', 20, 30, true);
simulateEvent('pointerdrag', 20, 30, true);
simulateEvent('pointermove', 30, 40, true);
@@ -398,6 +406,38 @@ describe('ol.interaction.Draw', function() {
expect(geometry.getCoordinates()).to.eql([[10, -20], [30, -20]]);
});
it('allows dragging of the vertex after dragVertexDelay', function(done) {
// first point
simulateEvent('pointermove', 10, 20);
simulateEvent('pointerdown', 10, 20);
simulateEvent('pointerup', 10, 20);
// second point, drag vertex
simulateEvent('pointermove', 15, 20);
simulateEvent('pointerdown', 15, 20);
setTimeout(function() {
simulateEvent('pointermove', 20, 10);
simulateEvent('pointerdrag', 20, 10);
simulateEvent('pointerup', 20, 10);
// third point
simulateEvent('pointermove', 30, 20);
simulateEvent('pointerdown', 30, 20);
simulateEvent('pointerup', 30, 20);
// finish on third point
simulateEvent('pointerdown', 30, 20);
simulateEvent('pointerup', 30, 20);
const features = source.getFeatures();
expect(features).to.have.length(1);
const geometry = features[0].getGeometry();
expect(geometry).to.be.a(LineString);
expect(geometry.getCoordinates()).to.eql([[10, -20], [20, -10], [30, -20]]);
done();
}, 600);
});
it('triggers draw events', function() {
var ds = sinon.spy();
var de = sinon.spy();