Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GSEPF-150: Add orthogonality to line drawing #5023

Merged
merged 5 commits into from
Jul 25, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion contribs/gmf/src/controllers/AbstractDesktopController.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,4 @@ exports.module.value('ngeoQueryOptions', {
exports.module.value('ngeoMeasurePrecision', 3);
exports.module.value('ngeoMeasureDecimals', 0);


export default exports;
59 changes: 56 additions & 3 deletions contribs/gmf/src/editing/Snapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ import olInteractionSnap from 'ol/interaction/Snap.js';
* @param {angular.$http} $http Angular $http service.
* @param {angular.$q} $q The Angular $q service.
* @param {!angular.Scope} $rootScope Angular rootScope.
* @param {!angular.$injector} $injector Angular injector.
* @param {angular.$timeout} $timeout Angular timeout service.
* @param {gmf.theme.Themes} gmfThemes The gmf Themes service.
* @param {gmf.layertree.TreeManager} gmfTreeManager The gmf TreeManager service.
* @ngInject
* @ngdoc service
* @ngname gmfSnapping
*/
const exports = function($http, $q, $rootScope, $timeout, gmfThemes,
const exports = function($http, $q, $rootScope, $injector, $timeout, gmfThemes,
gmfTreeManager) {

// === Injected services ===
Expand Down Expand Up @@ -62,6 +63,12 @@ const exports = function($http, $q, $rootScope, $timeout, gmfThemes,
*/
this.timeout_ = $timeout;

/**
* @type {!angular.$injector}
* @private
*/
this.injector_ = $injector;

/**
* @type {gmf.theme.Themes}
* @private
Expand Down Expand Up @@ -113,13 +120,46 @@ const exports = function($http, $q, $rootScope, $timeout, gmfThemes,
*/
this.ogcServers_ = null;

/**
* @type {ol.source.Vector | undefined}
* @private
*/
this.ngeoSnappingSource_ = this.injector_.get('ngeoSnappingSource') || undefined;

};


class CustomSnap extends olInteractionSnap {
constructor(options) {
super(options);
this.modifierPressed = false;
document.body.addEventListener('keydown', (evt) => {
this.modifierPressed = evt.keyCode === 17; // Ctrl key
});
document.body.addEventListener('keyup', () => {
this.modifierPressed = false;
});
}

handleEvent(evt) {
if (this.modifierPressed) {
return;
} else {
const result = this.snapTo(evt.pixel, evt.coordinate, evt.map);
if (result.snapped) {
evt.coordinate = result.vertex.slice(0, 2);
evt.pixel = result.vertexPixel;
}
return super.handleEvent(evt);
}
}
}


/**
* In order for a `ol.interaction.Snap` to work properly, it has to be added
* to the map after any draw interactions or other kinds of interactions that
* ineracts with features on the map.
* interacts with features on the map.
*
* This method can be called to make sure the Snap interactions are on top.
*
Expand Down Expand Up @@ -384,7 +424,7 @@ exports.prototype.activateItem_ = function(item) {
const map = this.map_;
googAsserts.assert(map);

const interaction = new olInteractionSnap({
const interaction = new CustomSnap({
edge: item.snappingConfig.edge,
features: item.features,
pixelTolerance: item.snappingConfig.tolerance,
Expand Down Expand Up @@ -514,6 +554,7 @@ exports.prototype.loadItemFeatures_ = function(item) {
const readFeatures = new olFormatWFS().readFeatures(response.data);
if (readFeatures) {
item.features.extend(readFeatures);
this.refreshSnappingSource_();
}
});

Expand All @@ -535,6 +576,18 @@ exports.prototype.handleMapMoveEnd_ = function() {
);
};

/**
* @private
*/
exports.prototype.refreshSnappingSource_ = function() {
this.ngeoSnappingSource_.clear();
for (const uid in this.cache_) {
const item = this.cache_[+uid];
llienher marked this conversation as resolved.
Show resolved Hide resolved
if (item.active) {
this.ngeoSnappingSource_.addFeatures(item.features.getArray());
}
}
};

/**
* @typedef {Object<number, gmf.editing.Snapping.CacheItem>}
Expand Down
1 change: 0 additions & 1 deletion examples/drawfeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ exports.module = angular.module('app', [
ngeoMiscToolActivateMgr.module.name,
]);


/**
* @param {!angular.Scope} $scope Angular scope.
* @param {ol.Collection.<ol.Feature>} ngeoFeatures Collection of features.
Expand Down
13 changes: 11 additions & 2 deletions src/editing/createfeatureComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ exports.directive('ngeoCreatefeature', exports.directive_);
* @param {!angularGettext.Catalog} gettextCatalog Gettext catalog.
* @param {!angular.$compile} $compile Angular compile service.
* @param {!angular.$filter} $filter Angular filter
* @param {!angular.$injector} $injector Angular injector service.
* @param {!angular.Scope} $scope Scope.
* @param {!angular.$timeout} $timeout Angular timeout service.
* @param {!ngeo.misc.EventHelper} ngeoEventHelper Ngeo event helper service
Expand All @@ -89,7 +90,7 @@ exports.directive('ngeoCreatefeature', exports.directive_);
* @ngdoc controller
* @ngname ngeoCreatefeatureController
*/
exports.Controller_ = function(gettextCatalog, $compile, $filter, $scope,
exports.Controller_ = function(gettextCatalog, $compile, $filter, $injector, $scope,
$timeout, ngeoEventHelper) {

/**
Expand Down Expand Up @@ -152,6 +153,12 @@ exports.Controller_ = function(gettextCatalog, $compile, $filter, $scope,
*/
this.ngeoEventHelper_ = ngeoEventHelper;

/**
* @type {!angular.$injector}
* @private
*/
this.injector_ = $injector;

/**
* The draw or measure interaction responsible of drawing the vector feature.
* The actual type depends on the geometry type.
Expand Down Expand Up @@ -201,7 +208,9 @@ exports.Controller_.prototype.$onInit = function() {
{
style: new olStyleStyle(),
startMsg: this.compile_(`<div translate>${helpMsg}</div>`)(this.scope_)[0],
continueMsg: this.compile_(`<div translate>${contMsg}</div>`)(this.scope_)[0]
continueMsg: this.compile_(`<div translate>${contMsg}</div>`)(this.scope_)[0],
tolerance: this.injector_.has('ngeoSnappingTolerance') ? this.injector_.get('ngeoSnappingTolerance') : undefined,
source: this.injector_.has('ngeoSnappingSource') ? this.injector_.get('ngeoSnappingSource') : undefined,
}
);
} else if (this.geomType === ngeoGeometryType.POLYGON ||
Expand Down
121 changes: 119 additions & 2 deletions src/interaction/MeasureLength.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import ngeoInteractionMeasure from 'ngeo/interaction/Measure.js';
import * as olBase from 'ol/index.js';
import olGeomLineString from 'ol/geom/LineString.js';
import olInteractionDraw from 'ol/interaction/Draw.js';
import {distance} from 'ol/coordinate.js';

let modifierPressed = undefined;


/**
* @classdesc
Expand All @@ -24,6 +28,15 @@ const exports = function(format, gettextCatalog, options = /** @type {ngeox.inte

ngeoInteractionMeasure.call(this, /** @type {ngeo.interaction.MeasureBaseOptions} */ (options));

if (modifierPressed === undefined) {
modifierPressed = false;
document.body.addEventListener('keydown', (evt) => {
modifierPressed = evt.keyCode === 17; // Ctrl key
});
document.body.addEventListener('keyup', () => {
modifierPressed = false;
});
}

if (options.continueMsg !== undefined) {
this.continueMsg = options.continueMsg;
Expand All @@ -41,6 +54,17 @@ const exports = function(format, gettextCatalog, options = /** @type {ngeox.inte
*/
this.format = format;

/**
* The snapping tolerance in pixels.
* @params {Number}
llienher marked this conversation as resolved.
Show resolved Hide resolved
*/
this.tolerance = options.tolerance;

/**
* The snapping source
* @params {ol.source.Vector}
*/
this.source = options.source;
};

olBase.inherits(exports, ngeoInteractionMeasure);
Expand All @@ -49,15 +73,74 @@ olBase.inherits(exports, ngeoInteractionMeasure);
/**
* @inheritDoc
*/
exports.prototype.createDrawInteraction = function(style, source) {
exports.prototype.createDrawInteraction = function(style) {
return new olInteractionDraw({
type: /** @type {ol.geom.GeometryType} */ ('LineString'),
source: source,
llienher marked this conversation as resolved.
Show resolved Hide resolved
geometryFunction: (...args) =>
this.linestringGeometryFunction(...args),
llienher marked this conversation as resolved.
Show resolved Hide resolved
condition: () => true,
llienher marked this conversation as resolved.
Show resolved Hide resolved
style: style
});
};


/**
* Create a `linestringGeometryFunction` that will create a line string with segments
* snapped to π/4 angle.
* Use this with the draw interaction and `type: 'LineString'`.
* @param {LineCoordType} coordinates Coordinates.
* @param {LineString=} opt_geometry Geometry.
* @return {LineString} Geometry.
*/
exports.prototype.linestringGeometryFunction = function(coordinates, opt_geometry) {
if (modifierPressed && this.tolerance !== undefined && this.source !== undefined) {
const viewRotation = this.getMap().getView().getRotation();
const angle = Math.PI / 4;
const from = coordinates[coordinates.length - 2];
const to = coordinates[coordinates.length - 1];
const dx = from[0] - to[0];
const dy = from[1] - to[1];
const length = distance(from, to);
const rotation = viewRotation + Math.round((Math.atan2(dy, dx) - viewRotation) / angle) * angle;

to[0] = from[0] - (length * Math.cos(rotation));
to[1] = from[1] - (length * Math.sin(rotation));

const delta = this.getMap().getView().getResolution() * this.tolerance;
const bbox = [to[0] - delta, to[1] - delta, to[0] + delta, to[1] + delta];

const layerSource = this.source;
const featuresInExtent = layerSource.getFeaturesInExtent(bbox);
if (featuresInExtent.length > 0) {
featuresInExtent.forEach((feature) => {
gberaudo marked this conversation as resolved.
Show resolved Hide resolved
feature.getGeometry().forEachSegment((point1, point2) => {
// Line points are: from A to M (to B that we need to find)
const distanceFromTo = distance(from, to);
const ax = from[0];
const ay = from[1];
const mx = to[0];
const my = to[1];
const unitVector = [(mx - ax) / distanceFromTo, (my - ay) / distanceFromTo];
const b = [(ax + (distanceFromTo + delta) * unitVector[0]), (ay + (distanceFromTo + delta) * unitVector[1])];
to[0] = b[0];
to[1] = b[1];

// intersection calculation
this.computeLineSegmentIntersection(from, to, point1, point2, to);
});
});
}
gberaudo marked this conversation as resolved.
Show resolved Hide resolved
}

const geometry = opt_geometry;
if (geometry) {
geometry.setCoordinates(coordinates);
return geometry;
}
return new olGeomLineString(coordinates);
};


/**
* @inheritDoc
*/
Expand All @@ -70,5 +153,39 @@ exports.prototype.handleMeasure = function(callback) {
callback(output, coord);
};

/**
* Compute the intersection between 2 segments
*
* @param {Number} line1vertex1 The coordinates of the first line's first vertex.
* @param {Number} line1vertex2 The coordinates of the first line's second vertex.
* @param {Number} line2vertex1 The coordinates of the second line's first vertex.
* @param {Number} line2vertex2 The coordinates of the second line's second vertex.
* @param {Array<number>} [result] An existing array where the result will be copied. If this parameter
* is undefined, a new array is created and returned.
* @returns {Array<number> | undefined} The intersection point, undefined if there is no intersection point or lines are coincident.
*/
exports.prototype.computeLineSegmentIntersection = function(line1vertex1, line1vertex2, line2vertex1, line2vertex2, result) {
const numerator1A = (line2vertex2[0] - line2vertex1[0]) * (line1vertex1[1] - line2vertex1[1])
- (line2vertex2[1] - line2vertex1[1]) * (line1vertex1[0] - line2vertex1[0]);
const numerator1B = (line1vertex2[0] - line1vertex1[0]) * (line1vertex1[1] - line2vertex1[1])
- (line1vertex2[1] - line1vertex1[1]) * (line1vertex1[0] - line2vertex1[0]);
const denominator1 = (line2vertex2[1] - line2vertex1[1]) * (line1vertex2[0] - line1vertex1[0])
- (line2vertex2[0] - line2vertex1[0]) * (line1vertex2[1] - line1vertex1[1]);

// If denominator = 0, then lines are parallel. If denominator = 0 and both numerators are 0, then coincident
if (denominator1 === 0) {
return;
}

const ua1 = numerator1A / denominator1;
const ub1 = numerator1B / denominator1;

if (ua1 >= 0 && ua1 <= 1 && ub1 >= 0 && ub1 <= 1) {
result[0] = line1vertex1[0] + ua1 * (line1vertex2[0] - line1vertex1[0]);
result[1] = line1vertex1[1] + ua1 * (line1vertex2[1] - line1vertex1[1]);
return result;
}
};


export default exports;
4 changes: 3 additions & 1 deletion src/measure/length.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ exports.directive_ = function($compile, gettextCatalog, $filter, $injector) {
style: new olStyleStyle(),
startMsg: $compile(`<div translate>${helpMsg}</div>`)($scope)[0],
continueMsg: $compile(`<div translate>${contMsg}</div>`)($scope)[0],
precision: $injector.has('ngeoMeasurePrecision') ? $injector.get('ngeoMeasurePrecision') : undefined
precision: $injector.has('ngeoMeasurePrecision') ? $injector.get('ngeoMeasurePrecision') : undefined,
tolerance: $injector.has('ngeoSnappingTolerance') ? $injector.get('ngeoSnappingTolerance') : undefined,
source: $injector.has('ngeoSnappingSource') ? $injector.get('ngeoSnappingSource') : undefined,
});

drawFeatureCtrl.registerInteraction(measureLength);
Expand Down