mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-23 00:29:50 +02:00
Merge pull request #3664 from openstreetmap/operation-behavior
Improved Post Paste Behaviors
This commit is contained in:
+58
-17
@@ -77,7 +77,9 @@ en:
|
||||
connected_to_hidden: This line can't be straightened because it is connected to a hidden feature.
|
||||
delete:
|
||||
title: Delete
|
||||
description: Delete object permanently.
|
||||
description:
|
||||
single: Delete this object permanently.
|
||||
multiple: Delete these objects permanently.
|
||||
annotation:
|
||||
point: Deleted a point.
|
||||
vertex: Deleted a node from a way.
|
||||
@@ -85,9 +87,15 @@ en:
|
||||
area: Deleted an area.
|
||||
relation: Deleted a relation.
|
||||
multiple: "Deleted {n} objects."
|
||||
incomplete_relation: This feature can't be deleted because it hasn't been fully downloaded.
|
||||
part_of_relation: This feature can't be deleted because it's part of a larger relation. You must remove it from the relation first.
|
||||
connected_to_hidden: This can't be deleted because it is connected to a hidden feature.
|
||||
incomplete_relation:
|
||||
single: This object can't be deleted because it hasn't been fully downloaded.
|
||||
multiple: These objects can't be deleted because they haven't been fully downloaded.
|
||||
part_of_relation:
|
||||
single: This object can't be deleted because it is part of a larger relation. You must remove it from the relation first.
|
||||
multiple: These objects can't be deleted because they are part of larger relations. You must remove them from the relations first.
|
||||
connected_to_hidden:
|
||||
single: This object can't be deleted because it is connected to a hidden feature.
|
||||
multiple: These objects can't be deleted because some are connected to hidden features.
|
||||
add_member:
|
||||
annotation: Added a member to a relation.
|
||||
delete_member:
|
||||
@@ -118,7 +126,9 @@ en:
|
||||
conflicting_tags: These features can't be merged because some of their tags have conflicting values.
|
||||
move:
|
||||
title: Move
|
||||
description: Move this to a different location.
|
||||
description:
|
||||
single: Move this object to a different location.
|
||||
multiple: Move these objects to a different location.
|
||||
key: M
|
||||
annotation:
|
||||
point: Moved a point.
|
||||
@@ -126,31 +136,62 @@ en:
|
||||
line: Moved a line.
|
||||
area: Moved an area.
|
||||
multiple: Moved multiple objects.
|
||||
incomplete_relation: This feature can't be moved because it hasn't been fully downloaded.
|
||||
too_large: This can't be moved because not enough of it is currently visible.
|
||||
connected_to_hidden: This can't be moved because it is connected to a hidden feature.
|
||||
incomplete_relation:
|
||||
single: This object can't be moved because it hasn't been fully downloaded.
|
||||
multiple: These objects can't be moved because they haven't been fully downloaded.
|
||||
too_large:
|
||||
single: This object can't be moved because not enough of it is currently visible.
|
||||
multiple: These objects can't be moved because not enough of them are currently visible.
|
||||
connected_to_hidden:
|
||||
single: This object can't be moved because it is connected to a hidden feature.
|
||||
multiple: These objects can't be moved because some are connected to hidden features.
|
||||
reflect:
|
||||
title: reflect
|
||||
description:
|
||||
long: Reflect this object across its long axis.
|
||||
short: Reflect this object across its short axis.
|
||||
long:
|
||||
single: Reflect this object across its long axis.
|
||||
multiple: Reflect these objects across their long axis.
|
||||
short:
|
||||
single: Reflect this object across its short axis.
|
||||
multiple: Reflect these objects across their short axis.
|
||||
key:
|
||||
long: T
|
||||
short: Y
|
||||
annotation:
|
||||
long: Reflected an area across its long axis.
|
||||
short: Reflected an area across its short axis.
|
||||
too_large: This can't be reflected because not enough of it is currently visible.
|
||||
connected_to_hidden: This can't be reflected because it is connected to a hidden feature.
|
||||
long:
|
||||
area: Reflected an area across its long axis.
|
||||
multiple: Reflected multiple objects across their long axis.
|
||||
short:
|
||||
area: Reflected an area across its short axis.
|
||||
multiple: Reflected multiple objects across their short axis.
|
||||
incomplete_relation:
|
||||
single: This object can't be reflected because it hasn't been fully downloaded.
|
||||
multiple: These objects can't be reflected because they haven't been fully downloaded.
|
||||
too_large:
|
||||
single: This object can't be reflected because not enough of it is currently visible.
|
||||
multiple: These objects can't be reflected because not enough of them are currently visible.
|
||||
connected_to_hidden:
|
||||
single: This object can't be reflected because it is connected to a hidden feature.
|
||||
multiple: These objects can't be reflected because some are connected to hidden features.
|
||||
rotate:
|
||||
title: Rotate
|
||||
description: Rotate this object around its center point.
|
||||
description:
|
||||
single: Rotate this object around its center point.
|
||||
multiple: Rotate these objects around their center point.
|
||||
key: R
|
||||
annotation:
|
||||
line: Rotated a line.
|
||||
area: Rotated an area.
|
||||
too_large: This can't be rotated because not enough of it is currently visible.
|
||||
connected_to_hidden: This can't be rotated because it is connected to a hidden feature.
|
||||
multiple: Rotated multiple objects.
|
||||
incomplete_relation:
|
||||
single: This object can't be rotated because it hasn't been fully downloaded.
|
||||
multiple: These objects can't be rotated because they haven't been fully downloaded.
|
||||
too_large:
|
||||
single: This object can't be rotated because not enough of it is currently visible.
|
||||
multiple: These objects can't be rotated because not enough of them are currently visible.
|
||||
connected_to_hidden:
|
||||
single: This object can't be rotated because it is connected to a hidden feature.
|
||||
multiple: These objects can't be rotated because some are connected to hidden features.
|
||||
reverse:
|
||||
title: Reverse
|
||||
description: Make this line go in the opposite direction.
|
||||
|
||||
Vendored
+78
-18
@@ -101,7 +101,10 @@
|
||||
},
|
||||
"delete": {
|
||||
"title": "Delete",
|
||||
"description": "Delete object permanently.",
|
||||
"description": {
|
||||
"single": "Delete this object permanently.",
|
||||
"multiple": "Delete these objects permanently."
|
||||
},
|
||||
"annotation": {
|
||||
"point": "Deleted a point.",
|
||||
"vertex": "Deleted a node from a way.",
|
||||
@@ -110,9 +113,18 @@
|
||||
"relation": "Deleted a relation.",
|
||||
"multiple": "Deleted {n} objects."
|
||||
},
|
||||
"incomplete_relation": "This feature can't be deleted because it hasn't been fully downloaded.",
|
||||
"part_of_relation": "This feature can't be deleted because it's part of a larger relation. You must remove it from the relation first.",
|
||||
"connected_to_hidden": "This can't be deleted because it is connected to a hidden feature."
|
||||
"incomplete_relation": {
|
||||
"single": "This object can't be deleted because it hasn't been fully downloaded.",
|
||||
"multiple": "These objects can't be deleted because they haven't been fully downloaded."
|
||||
},
|
||||
"part_of_relation": {
|
||||
"single": "This object can't be deleted because it is part of a larger relation. You must remove it from the relation first.",
|
||||
"multiple": "These objects can't be deleted because they are part of larger relations. You must remove them from the relations first."
|
||||
},
|
||||
"connected_to_hidden": {
|
||||
"single": "This object can't be deleted because it is connected to a hidden feature.",
|
||||
"multiple": "These objects can't be deleted because some are connected to hidden features."
|
||||
}
|
||||
},
|
||||
"add_member": {
|
||||
"annotation": "Added a member to a relation."
|
||||
@@ -150,7 +162,10 @@
|
||||
},
|
||||
"move": {
|
||||
"title": "Move",
|
||||
"description": "Move this to a different location.",
|
||||
"description": {
|
||||
"single": "Move this object to a different location.",
|
||||
"multiple": "Move these objects to a different location."
|
||||
},
|
||||
"key": "M",
|
||||
"annotation": {
|
||||
"point": "Moved a point.",
|
||||
@@ -159,37 +174,82 @@
|
||||
"area": "Moved an area.",
|
||||
"multiple": "Moved multiple objects."
|
||||
},
|
||||
"incomplete_relation": "This feature can't be moved because it hasn't been fully downloaded.",
|
||||
"too_large": "This can't be moved because not enough of it is currently visible.",
|
||||
"connected_to_hidden": "This can't be moved because it is connected to a hidden feature."
|
||||
"incomplete_relation": {
|
||||
"single": "This object can't be moved because it hasn't been fully downloaded.",
|
||||
"multiple": "These objects can't be moved because they haven't been fully downloaded."
|
||||
},
|
||||
"too_large": {
|
||||
"single": "This object can't be moved because not enough of it is currently visible.",
|
||||
"multiple": "These objects can't be moved because not enough of them are currently visible."
|
||||
},
|
||||
"connected_to_hidden": {
|
||||
"single": "This object can't be moved because it is connected to a hidden feature.",
|
||||
"multiple": "These objects can't be moved because some are connected to hidden features."
|
||||
}
|
||||
},
|
||||
"reflect": {
|
||||
"title": "reflect",
|
||||
"description": {
|
||||
"long": "Reflect this object across its long axis.",
|
||||
"short": "Reflect this object across its short axis."
|
||||
"long": {
|
||||
"single": "Reflect this object across its long axis.",
|
||||
"multiple": "Reflect these objects across their long axis."
|
||||
},
|
||||
"short": {
|
||||
"single": "Reflect this object across its short axis.",
|
||||
"multiple": "Reflect these objects across their short axis."
|
||||
}
|
||||
},
|
||||
"key": {
|
||||
"long": "T",
|
||||
"short": "Y"
|
||||
},
|
||||
"annotation": {
|
||||
"long": "Reflected an area across its long axis.",
|
||||
"short": "Reflected an area across its short axis."
|
||||
"long": {
|
||||
"area": "Reflected an area across its long axis.",
|
||||
"multiple": "Reflected multiple objects across their long axis."
|
||||
},
|
||||
"short": {
|
||||
"area": "Reflected an area across its short axis.",
|
||||
"multiple": "Reflected multiple objects across their short axis."
|
||||
}
|
||||
},
|
||||
"too_large": "This can't be reflected because not enough of it is currently visible.",
|
||||
"connected_to_hidden": "This can't be reflected because it is connected to a hidden feature."
|
||||
"incomplete_relation": {
|
||||
"single": "This object can't be reflected because it hasn't been fully downloaded.",
|
||||
"multiple": "These objects can't be reflected because they haven't been fully downloaded."
|
||||
},
|
||||
"too_large": {
|
||||
"single": "This object can't be reflected because not enough of it is currently visible.",
|
||||
"multiple": "These objects can't be reflected because not enough of them are currently visible."
|
||||
},
|
||||
"connected_to_hidden": {
|
||||
"single": "This object can't be reflected because it is connected to a hidden feature.",
|
||||
"multiple": "These objects can't be reflected because some are connected to hidden features."
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"title": "Rotate",
|
||||
"description": "Rotate this object around its center point.",
|
||||
"description": {
|
||||
"single": "Rotate this object around its center point.",
|
||||
"multiple": "Rotate these objects around their center point."
|
||||
},
|
||||
"key": "R",
|
||||
"annotation": {
|
||||
"line": "Rotated a line.",
|
||||
"area": "Rotated an area."
|
||||
"area": "Rotated an area.",
|
||||
"multiple": "Rotated multiple objects."
|
||||
},
|
||||
"too_large": "This can't be rotated because not enough of it is currently visible.",
|
||||
"connected_to_hidden": "This can't be rotated because it is connected to a hidden feature."
|
||||
"incomplete_relation": {
|
||||
"single": "This object can't be rotated because it hasn't been fully downloaded.",
|
||||
"multiple": "These objects can't be rotated because they haven't been fully downloaded."
|
||||
},
|
||||
"too_large": {
|
||||
"single": "This object can't be rotated because not enough of it is currently visible.",
|
||||
"multiple": "These objects can't be rotated because not enough of them are currently visible."
|
||||
},
|
||||
"connected_to_hidden": {
|
||||
"single": "This object can't be rotated because it is connected to a hidden feature.",
|
||||
"multiple": "These objects can't be rotated because some are connected to hidden features."
|
||||
}
|
||||
},
|
||||
"reverse": {
|
||||
"title": "Reverse",
|
||||
|
||||
@@ -22,14 +22,5 @@ export function actionDeleteMultiple(ids) {
|
||||
};
|
||||
|
||||
|
||||
action.disabled = function(graph) {
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var id = ids[i],
|
||||
disabled = actions[graph.entity(id).type](id).disabled(graph);
|
||||
if (disabled) return disabled;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -31,10 +31,5 @@ export function actionDeleteNode(nodeId) {
|
||||
};
|
||||
|
||||
|
||||
action.disabled = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -39,11 +39,5 @@ export function actionDeleteRelation(relationId) {
|
||||
};
|
||||
|
||||
|
||||
action.disabled = function(graph) {
|
||||
if (!graph.entity(relationId).isComplete(graph))
|
||||
return 'incomplete_relation';
|
||||
};
|
||||
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -39,20 +39,5 @@ export function actionDeleteWay(wayId) {
|
||||
};
|
||||
|
||||
|
||||
action.disabled = function(graph) {
|
||||
var disabled = false;
|
||||
|
||||
graph.parentRelations(graph.entity(wayId)).forEach(function(parent) {
|
||||
var type = parent.tags.type,
|
||||
role = parent.memberById(wayId).role || 'outer';
|
||||
if (type === 'route' || type === 'boundary' || (type === 'multipolygon' && role === 'outer')) {
|
||||
disabled = 'part_of_relation';
|
||||
}
|
||||
});
|
||||
|
||||
return disabled;
|
||||
};
|
||||
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export { actionOrthogonalize } from './orthogonalize';
|
||||
export { actionRestrictTurn } from './restrict_turn';
|
||||
export { actionReverse } from './reverse';
|
||||
export { actionRevert } from './revert';
|
||||
export { actionRotateWay } from './rotate_way';
|
||||
export { actionRotate } from './rotate';
|
||||
export { actionSplit } from './split';
|
||||
export { actionStraighten } from './straighten';
|
||||
export { actionUnrestrictTurn } from './unrestrict_turn';
|
||||
|
||||
@@ -286,17 +286,6 @@ export function actionMove(moveIds, tryDelta, projection, cache) {
|
||||
};
|
||||
|
||||
|
||||
action.disabled = function(graph) {
|
||||
function incompleteRelation(id) {
|
||||
var entity = graph.entity(id);
|
||||
return entity.type === 'relation' && !entity.isComplete(graph);
|
||||
}
|
||||
|
||||
if (_.some(moveIds, incompleteRelation))
|
||||
return 'incomplete_relation';
|
||||
};
|
||||
|
||||
|
||||
action.delta = function() {
|
||||
return delta;
|
||||
};
|
||||
|
||||
+14
-27
@@ -1,4 +1,3 @@
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
polygonHull as d3polygonHull,
|
||||
polygonCentroid as d3polygonCentroid
|
||||
@@ -6,29 +5,22 @@ import {
|
||||
|
||||
import {
|
||||
geoEuclideanDistance,
|
||||
geoExtent
|
||||
geoExtent,
|
||||
geoRotate
|
||||
} from '../geo';
|
||||
|
||||
import { utilGetAllNodes } from '../util';
|
||||
|
||||
|
||||
/* Reflect the given area around its axis of symmetry */
|
||||
export function actionReflect(wayId, projection) {
|
||||
export function actionReflect(reflectIds, projection) {
|
||||
var useLongAxis = true;
|
||||
|
||||
function rotatePolygon(polygon, angle, centroid) {
|
||||
return polygon.map(function(point) {
|
||||
var radial = [point[0] - centroid[0], point[1] - centroid[1]];
|
||||
return [
|
||||
radial[0] * Math.cos(angle) - radial[1] * Math.sin(angle) + centroid[0],
|
||||
radial[0] * Math.sin(angle) + radial[1] * Math.cos(angle) + centroid[1]
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
// http://gis.stackexchange.com/questions/22895/finding-minimum-area-rectangle-for-given-points
|
||||
// http://gis.stackexchange.com/questions/3739/generalisation-strategies-for-building-outlines/3756#3756
|
||||
function getSmallestSurroundingRectangle(graph, way) {
|
||||
var nodes = _.uniq(graph.childNodes(way)),
|
||||
points = nodes.map(function(n) { return projection(n.loc); }),
|
||||
function getSmallestSurroundingRectangle(graph, nodes) {
|
||||
var points = nodes.map(function(n) { return projection(n.loc); }),
|
||||
hull = d3polygonHull(points),
|
||||
centroid = d3polygonCentroid(hull),
|
||||
minArea = Infinity,
|
||||
@@ -39,7 +31,7 @@ export function actionReflect(wayId, projection) {
|
||||
for (var i = 0; i < hull.length - 1; i++) {
|
||||
var c2 = hull[i + 1],
|
||||
angle = Math.atan2(c2[1] - c1[1], c2[0] - c1[0]),
|
||||
poly = rotatePolygon(hull, -angle, centroid),
|
||||
poly = geoRotate(hull, -angle, centroid),
|
||||
extent = poly.reduce(function(extent, point) {
|
||||
return extent.extend(geoExtent(point));
|
||||
}, geoExtent()),
|
||||
@@ -54,20 +46,15 @@ export function actionReflect(wayId, projection) {
|
||||
}
|
||||
|
||||
return {
|
||||
poly: rotatePolygon(ssrExtent.polygon(), ssrAngle, centroid),
|
||||
poly: geoRotate(ssrExtent.polygon(), ssrAngle, centroid),
|
||||
angle: ssrAngle
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var action = function (graph) {
|
||||
var targetWay = graph.entity(wayId);
|
||||
if (!targetWay.isArea()) {
|
||||
return graph;
|
||||
}
|
||||
|
||||
var ssr = getSmallestSurroundingRectangle(graph, targetWay),
|
||||
nodes = targetWay.nodes;
|
||||
var action = function(graph) {
|
||||
var nodes = utilGetAllNodes(reflectIds, graph),
|
||||
ssr = getSmallestSurroundingRectangle(graph, nodes);
|
||||
|
||||
// Choose line pq = axis of symmetry.
|
||||
// The shape's surrounding rectangle has 2 axes of symmetry.
|
||||
@@ -93,8 +80,8 @@ export function actionReflect(wayId, projection) {
|
||||
var dy = q[1] - p[1];
|
||||
var a = (dx * dx - dy * dy) / (dx * dx + dy * dy);
|
||||
var b = 2 * dx * dy / (dx * dx + dy * dy);
|
||||
for (var i = 0; i < nodes.length - 1; i++) {
|
||||
var node = graph.entity(nodes[i]);
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
var c = projection(node.loc);
|
||||
var c2 = [
|
||||
a * (c[0] - p[0]) + b * (c[1] - p[1]) + p[0],
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { geoRotate } from '../geo';
|
||||
import { utilGetAllNodes } from '../util';
|
||||
|
||||
export function actionRotate(rotateIds, pivot, angle, projection) {
|
||||
|
||||
var action = function(graph) {
|
||||
return graph.update(function(graph) {
|
||||
utilGetAllNodes(rotateIds, graph).forEach(function(node) {
|
||||
var point = geoRotate([projection(node.loc)], angle, pivot)[0];
|
||||
graph = graph.replace(node.move(projection.invert(point)));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export function actionRotateWay(wayId, pivot, angle, projection) {
|
||||
return function(graph) {
|
||||
return graph.update(function(graph) {
|
||||
var way = graph.entity(wayId);
|
||||
|
||||
_.uniq(way.nodes).forEach(function(id) {
|
||||
|
||||
var node = graph.entity(id),
|
||||
point = projection(node.loc),
|
||||
radial = [0,0];
|
||||
|
||||
radial[0] = point[0] - pivot[0];
|
||||
radial[1] = point[1] - pivot[1];
|
||||
|
||||
point = [
|
||||
radial[0] * Math.cos(angle) - radial[1] * Math.sin(angle) + pivot[0],
|
||||
radial[0] * Math.sin(angle) + radial[1] * Math.cos(angle) + pivot[1]
|
||||
];
|
||||
|
||||
graph = graph.replace(node.move(projection.invert(point)));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -8,6 +8,7 @@ export { behaviorEdit } from './edit';
|
||||
export { behaviorHash } from './hash';
|
||||
export { behaviorHover } from './hover';
|
||||
export { behaviorLasso } from './lasso';
|
||||
export { behaviorOperation } from './operation';
|
||||
export { behaviorPaste } from './paste';
|
||||
export { behaviorSelect } from './select';
|
||||
export { behaviorTail } from './tail';
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import * as d3 from 'd3';
|
||||
import { d3keybinding } from '../lib/d3.keybinding.js';
|
||||
|
||||
|
||||
/* Creates a keybinding behavior for an operation */
|
||||
export function behaviorOperation(context) {
|
||||
var which, keybinding;
|
||||
|
||||
|
||||
var behavior = function () {
|
||||
if (which) {
|
||||
keybinding = d3keybinding('behavior.key.' + which.id);
|
||||
keybinding.on(which.keys, function() {
|
||||
d3.event.preventDefault();
|
||||
if (which.available() && !which.disabled() && !context.inIntro()) {
|
||||
which();
|
||||
}
|
||||
});
|
||||
d3.select(document).call(keybinding);
|
||||
}
|
||||
return behavior;
|
||||
};
|
||||
|
||||
|
||||
behavior.off = function() {
|
||||
if (keybinding) {
|
||||
d3.select(document).call(keybinding.off);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
behavior.which = function (_) {
|
||||
if (!arguments.length) return which;
|
||||
which = _;
|
||||
return behavior;
|
||||
};
|
||||
|
||||
|
||||
return behavior;
|
||||
}
|
||||
@@ -59,15 +59,29 @@ export function behaviorPaste(context) {
|
||||
context.perform(action);
|
||||
|
||||
var copies = action.copies();
|
||||
var originals = _.invert(_.mapValues(copies, 'id'));
|
||||
for (var id in copies) {
|
||||
var oldEntity = oldGraph.entity(id),
|
||||
newEntity = copies[id];
|
||||
|
||||
extent._extend(oldEntity.extent(oldGraph));
|
||||
newIDs.push(newEntity.id);
|
||||
context.perform(
|
||||
actionChangeTags(newEntity.id, _.omit(newEntity.tags, omitTag))
|
||||
);
|
||||
|
||||
// Exclude child nodes from newIDs if their parent way was also copied.
|
||||
var parents = context.graph().parentWays(newEntity),
|
||||
parentCopied = false;
|
||||
for (var i = 0; i < parents.length; i++) {
|
||||
if (originals[parents[i].id]) {
|
||||
parentCopied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parentCopied) {
|
||||
newIDs.push(newEntity.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Put pasted objects where mouse pointer is..
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
export function geoRoundCoords(c) {
|
||||
return [Math.floor(c[0]), Math.floor(c[1])];
|
||||
}
|
||||
|
||||
|
||||
export function geoInterp(p1, p2, t) {
|
||||
return [p1[0] + (p2[0] - p1[0]) * t,
|
||||
p1[1] + (p2[1] - p1[1]) * t];
|
||||
}
|
||||
|
||||
|
||||
// 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
|
||||
// Returns a positive value, if OAB makes a counter-clockwise turn,
|
||||
// negative for clockwise turn, and zero if the points are collinear.
|
||||
export function geoCross(o, a, b) {
|
||||
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
|
||||
}
|
||||
|
||||
|
||||
// http://jsperf.com/id-dist-optimization
|
||||
export function geoEuclideanDistance(a, b) {
|
||||
var x = a[0] - b[0], y = a[1] - b[1];
|
||||
return Math.sqrt((x * x) + (y * y));
|
||||
}
|
||||
|
||||
|
||||
// using WGS84 polar radius (6356752.314245179 m)
|
||||
// const = 2 * PI * r / 360
|
||||
export function geoLatToMeters(dLat) {
|
||||
return dLat * 110946.257617;
|
||||
}
|
||||
|
||||
|
||||
// using WGS84 equatorial radius (6378137.0 m)
|
||||
// const = 2 * PI * r / 360
|
||||
export function geoLonToMeters(dLon, atLat) {
|
||||
return Math.abs(atLat) >= 90 ? 0 :
|
||||
dLon * 111319.490793 * Math.abs(Math.cos(atLat * (Math.PI/180)));
|
||||
}
|
||||
|
||||
|
||||
// using WGS84 polar radius (6356752.314245179 m)
|
||||
// const = 2 * PI * r / 360
|
||||
export function geoMetersToLat(m) {
|
||||
return m / 110946.257617;
|
||||
}
|
||||
|
||||
|
||||
// using WGS84 equatorial radius (6378137.0 m)
|
||||
// const = 2 * PI * r / 360
|
||||
export function geoMetersToLon(m, atLat) {
|
||||
return Math.abs(atLat) >= 90 ? 0 :
|
||||
m / 111319.490793 / Math.abs(Math.cos(atLat * (Math.PI/180)));
|
||||
}
|
||||
|
||||
|
||||
export function geoOffsetToMeters(offset) {
|
||||
var equatRadius = 6356752.314245179,
|
||||
polarRadius = 6378137.0,
|
||||
tileSize = 256;
|
||||
|
||||
return [
|
||||
offset[0] * 2 * Math.PI * equatRadius / tileSize,
|
||||
-offset[1] * 2 * Math.PI * polarRadius / tileSize
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
export function geoMetersToOffset(meters) {
|
||||
var equatRadius = 6356752.314245179,
|
||||
polarRadius = 6378137.0,
|
||||
tileSize = 256;
|
||||
|
||||
return [
|
||||
meters[0] * tileSize / (2 * Math.PI * equatRadius),
|
||||
-meters[1] * tileSize / (2 * Math.PI * polarRadius)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Equirectangular approximation of spherical distances on Earth
|
||||
export function geoSphericalDistance(a, b) {
|
||||
var x = geoLonToMeters(a[0] - b[0], (a[1] + b[1]) / 2),
|
||||
y = geoLatToMeters(a[1] - b[1]);
|
||||
return Math.sqrt((x * x) + (y * y));
|
||||
}
|
||||
|
||||
|
||||
export function geoEdgeEqual(a, b) {
|
||||
return (a[0] === b[0] && a[1] === b[1]) ||
|
||||
(a[0] === b[1] && a[1] === b[0]);
|
||||
}
|
||||
|
||||
|
||||
// Return the counterclockwise angle in the range (-pi, pi)
|
||||
// between the positive X axis and the line intersecting a and b.
|
||||
export function geoAngle(a, b, projection) {
|
||||
a = projection(a.loc);
|
||||
b = projection(b.loc);
|
||||
return Math.atan2(b[1] - a[1], b[0] - a[0]);
|
||||
}
|
||||
|
||||
|
||||
// Rotate all points counterclockwise around a pivot point by given angle
|
||||
export function geoRotate(points, angle, around) {
|
||||
return points.map(function(point) {
|
||||
var radial = [point[0] - around[0], point[1] - around[1]];
|
||||
return [
|
||||
radial[0] * Math.cos(angle) - radial[1] * Math.sin(angle) + around[0],
|
||||
radial[0] * Math.sin(angle) + radial[1] * Math.cos(angle) + around[1]
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Choose the edge with the minimal distance from `point` to its orthogonal
|
||||
// projection onto that edge, if such a projection exists, or the distance to
|
||||
// the closest vertex on that edge. Returns an object with the `index` of the
|
||||
// chosen edge, the chosen `loc` on that edge, and the `distance` to to it.
|
||||
export function geoChooseEdge(nodes, point, projection) {
|
||||
var dist = geoEuclideanDistance,
|
||||
points = nodes.map(function(n) { return projection(n.loc); }),
|
||||
min = Infinity,
|
||||
idx, loc;
|
||||
|
||||
function dot(p, q) {
|
||||
return p[0] * q[0] + p[1] * q[1];
|
||||
}
|
||||
|
||||
for (var i = 0; i < points.length - 1; i++) {
|
||||
var o = points[i],
|
||||
s = [points[i + 1][0] - o[0],
|
||||
points[i + 1][1] - o[1]],
|
||||
v = [point[0] - o[0],
|
||||
point[1] - o[1]],
|
||||
proj = dot(v, s) / dot(s, s),
|
||||
p;
|
||||
|
||||
if (proj < 0) {
|
||||
p = o;
|
||||
} else if (proj > 1) {
|
||||
p = points[i + 1];
|
||||
} else {
|
||||
p = [o[0] + proj * s[0], o[1] + proj * s[1]];
|
||||
}
|
||||
|
||||
var d = dist(p, point);
|
||||
if (d < min) {
|
||||
min = d;
|
||||
idx = i + 1;
|
||||
loc = projection.invert(p);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
index: idx,
|
||||
distance: min,
|
||||
loc: loc
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Return the intersection point of 2 line segments.
|
||||
// From https://github.com/pgkelley4/line-segments-intersect
|
||||
// This uses the vector cross product approach described below:
|
||||
// http://stackoverflow.com/a/565282/786339
|
||||
export function geoLineIntersection(a, b) {
|
||||
function subtractPoints(point1, point2) {
|
||||
return [point1[0] - point2[0], point1[1] - point2[1]];
|
||||
}
|
||||
function crossProduct(point1, point2) {
|
||||
return point1[0] * point2[1] - point1[1] * point2[0];
|
||||
}
|
||||
|
||||
var p = [a[0][0], a[0][1]],
|
||||
p2 = [a[1][0], a[1][1]],
|
||||
q = [b[0][0], b[0][1]],
|
||||
q2 = [b[1][0], b[1][1]],
|
||||
r = subtractPoints(p2, p),
|
||||
s = subtractPoints(q2, q),
|
||||
uNumerator = crossProduct(subtractPoints(q, p), r),
|
||||
denominator = crossProduct(r, s);
|
||||
|
||||
if (uNumerator && denominator) {
|
||||
var u = uNumerator / denominator,
|
||||
t = crossProduct(subtractPoints(q, p), s) / denominator;
|
||||
|
||||
if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) {
|
||||
return geoInterp(p, p2, t);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
export function geoPathIntersections(path1, path2) {
|
||||
var intersections = [];
|
||||
for (var i = 0; i < path1.length - 1; i++) {
|
||||
for (var j = 0; j < path2.length - 1; j++) {
|
||||
var a = [ path1[i], path1[i+1] ],
|
||||
b = [ path2[j], path2[j+1] ],
|
||||
hit = geoLineIntersection(a, b);
|
||||
if (hit) intersections.push(hit);
|
||||
}
|
||||
}
|
||||
return intersections;
|
||||
}
|
||||
|
||||
|
||||
// Return whether point is contained in polygon.
|
||||
//
|
||||
// `point` should be a 2-item array of coordinates.
|
||||
// `polygon` should be an array of 2-item arrays of coordinates.
|
||||
//
|
||||
// From https://github.com/substack/point-in-polygon.
|
||||
// ray-casting algorithm based on
|
||||
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
|
||||
//
|
||||
export function geoPointInPolygon(point, polygon) {
|
||||
var x = point[0],
|
||||
y = point[1],
|
||||
inside = false;
|
||||
|
||||
for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||
var xi = polygon[i][0], yi = polygon[i][1];
|
||||
var xj = polygon[j][0], yj = polygon[j][1];
|
||||
|
||||
var intersect = ((yi > y) !== (yj > y)) &&
|
||||
(x < (xj - xi) * (y - yi) / (yj - yi) + xi);
|
||||
if (intersect) inside = !inside;
|
||||
}
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
|
||||
export function geoPolygonContainsPolygon(outer, inner) {
|
||||
return _.every(inner, function(point) {
|
||||
return geoPointInPolygon(point, outer);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function geoPolygonIntersectsPolygon(outer, inner, checkSegments) {
|
||||
function testSegments(outer, inner) {
|
||||
for (var i = 0; i < outer.length - 1; i++) {
|
||||
for (var j = 0; j < inner.length - 1; j++) {
|
||||
var a = [ outer[i], outer[i+1] ],
|
||||
b = [ inner[j], inner[j+1] ];
|
||||
if (geoLineIntersection(a, b)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function testPoints(outer, inner) {
|
||||
return _.some(inner, function(point) {
|
||||
return geoPointInPolygon(point, outer);
|
||||
});
|
||||
}
|
||||
|
||||
return testPoints(outer, inner) || (!!checkSegments && testSegments(outer, inner));
|
||||
}
|
||||
|
||||
|
||||
export function geoPathLength(path) {
|
||||
var length = 0;
|
||||
for (var i = 0; i < path.length - 1; i++) {
|
||||
length += geoEuclideanDistance(path[i], path[i + 1]);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
+21
-265
@@ -1,267 +1,23 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export { geoAngle } from './geo.js';
|
||||
export { geoChooseEdge } from './geo.js';
|
||||
export { geoCross } from './geo.js';
|
||||
export { geoEdgeEqual } from './geo.js';
|
||||
export { geoEuclideanDistance } from './geo.js';
|
||||
export { geoExtent } from './extent.js';
|
||||
export { geoInterp } from './geo.js';
|
||||
export { geoRawMercator } from './raw_mercator.js';
|
||||
|
||||
|
||||
export function geoRoundCoords(c) {
|
||||
return [Math.floor(c[0]), Math.floor(c[1])];
|
||||
}
|
||||
|
||||
|
||||
export function geoInterp(p1, p2, t) {
|
||||
return [p1[0] + (p2[0] - p1[0]) * t,
|
||||
p1[1] + (p2[1] - p1[1]) * t];
|
||||
}
|
||||
|
||||
|
||||
// 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
|
||||
// Returns a positive value, if OAB makes a counter-clockwise turn,
|
||||
// negative for clockwise turn, and zero if the points are collinear.
|
||||
export function geoCross(o, a, b) {
|
||||
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
|
||||
}
|
||||
|
||||
|
||||
// http://jsperf.com/id-dist-optimization
|
||||
export function geoEuclideanDistance(a, b) {
|
||||
var x = a[0] - b[0], y = a[1] - b[1];
|
||||
return Math.sqrt((x * x) + (y * y));
|
||||
}
|
||||
|
||||
|
||||
// using WGS84 polar radius (6356752.314245179 m)
|
||||
// const = 2 * PI * r / 360
|
||||
export function geoLatToMeters(dLat) {
|
||||
return dLat * 110946.257617;
|
||||
}
|
||||
|
||||
|
||||
// using WGS84 equatorial radius (6378137.0 m)
|
||||
// const = 2 * PI * r / 360
|
||||
export function geoLonToMeters(dLon, atLat) {
|
||||
return Math.abs(atLat) >= 90 ? 0 :
|
||||
dLon * 111319.490793 * Math.abs(Math.cos(atLat * (Math.PI/180)));
|
||||
}
|
||||
|
||||
|
||||
// using WGS84 polar radius (6356752.314245179 m)
|
||||
// const = 2 * PI * r / 360
|
||||
export function geoMetersToLat(m) {
|
||||
return m / 110946.257617;
|
||||
}
|
||||
|
||||
|
||||
// using WGS84 equatorial radius (6378137.0 m)
|
||||
// const = 2 * PI * r / 360
|
||||
export function geoMetersToLon(m, atLat) {
|
||||
return Math.abs(atLat) >= 90 ? 0 :
|
||||
m / 111319.490793 / Math.abs(Math.cos(atLat * (Math.PI/180)));
|
||||
}
|
||||
|
||||
|
||||
export function geoOffsetToMeters(offset) {
|
||||
var equatRadius = 6356752.314245179,
|
||||
polarRadius = 6378137.0,
|
||||
tileSize = 256;
|
||||
|
||||
return [
|
||||
offset[0] * 2 * Math.PI * equatRadius / tileSize,
|
||||
-offset[1] * 2 * Math.PI * polarRadius / tileSize
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
export function geoMetersToOffset(meters) {
|
||||
var equatRadius = 6356752.314245179,
|
||||
polarRadius = 6378137.0,
|
||||
tileSize = 256;
|
||||
|
||||
return [
|
||||
meters[0] * tileSize / (2 * Math.PI * equatRadius),
|
||||
-meters[1] * tileSize / (2 * Math.PI * polarRadius)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Equirectangular approximation of spherical distances on Earth
|
||||
export function geoSphericalDistance(a, b) {
|
||||
var x = geoLonToMeters(a[0] - b[0], (a[1] + b[1]) / 2),
|
||||
y = geoLatToMeters(a[1] - b[1]);
|
||||
return Math.sqrt((x * x) + (y * y));
|
||||
}
|
||||
|
||||
|
||||
export function geoEdgeEqual(a, b) {
|
||||
return (a[0] === b[0] && a[1] === b[1]) ||
|
||||
(a[0] === b[1] && a[1] === b[0]);
|
||||
}
|
||||
|
||||
|
||||
// Return the counterclockwise angle in the range (-pi, pi)
|
||||
// between the positive X axis and the line intersecting a and b.
|
||||
export function geoAngle(a, b, projection) {
|
||||
a = projection(a.loc);
|
||||
b = projection(b.loc);
|
||||
return Math.atan2(b[1] - a[1], b[0] - a[0]);
|
||||
}
|
||||
|
||||
|
||||
// Choose the edge with the minimal distance from `point` to its orthogonal
|
||||
// projection onto that edge, if such a projection exists, or the distance to
|
||||
// the closest vertex on that edge. Returns an object with the `index` of the
|
||||
// chosen edge, the chosen `loc` on that edge, and the `distance` to to it.
|
||||
export function geoChooseEdge(nodes, point, projection) {
|
||||
var dist = geoEuclideanDistance,
|
||||
points = nodes.map(function(n) { return projection(n.loc); }),
|
||||
min = Infinity,
|
||||
idx, loc;
|
||||
|
||||
function dot(p, q) {
|
||||
return p[0] * q[0] + p[1] * q[1];
|
||||
}
|
||||
|
||||
for (var i = 0; i < points.length - 1; i++) {
|
||||
var o = points[i],
|
||||
s = [points[i + 1][0] - o[0],
|
||||
points[i + 1][1] - o[1]],
|
||||
v = [point[0] - o[0],
|
||||
point[1] - o[1]],
|
||||
proj = dot(v, s) / dot(s, s),
|
||||
p;
|
||||
|
||||
if (proj < 0) {
|
||||
p = o;
|
||||
} else if (proj > 1) {
|
||||
p = points[i + 1];
|
||||
} else {
|
||||
p = [o[0] + proj * s[0], o[1] + proj * s[1]];
|
||||
}
|
||||
|
||||
var d = dist(p, point);
|
||||
if (d < min) {
|
||||
min = d;
|
||||
idx = i + 1;
|
||||
loc = projection.invert(p);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
index: idx,
|
||||
distance: min,
|
||||
loc: loc
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Return the intersection point of 2 line segments.
|
||||
// From https://github.com/pgkelley4/line-segments-intersect
|
||||
// This uses the vector cross product approach described below:
|
||||
// http://stackoverflow.com/a/565282/786339
|
||||
export function geoLineIntersection(a, b) {
|
||||
function subtractPoints(point1, point2) {
|
||||
return [point1[0] - point2[0], point1[1] - point2[1]];
|
||||
}
|
||||
function crossProduct(point1, point2) {
|
||||
return point1[0] * point2[1] - point1[1] * point2[0];
|
||||
}
|
||||
|
||||
var p = [a[0][0], a[0][1]],
|
||||
p2 = [a[1][0], a[1][1]],
|
||||
q = [b[0][0], b[0][1]],
|
||||
q2 = [b[1][0], b[1][1]],
|
||||
r = subtractPoints(p2, p),
|
||||
s = subtractPoints(q2, q),
|
||||
uNumerator = crossProduct(subtractPoints(q, p), r),
|
||||
denominator = crossProduct(r, s);
|
||||
|
||||
if (uNumerator && denominator) {
|
||||
var u = uNumerator / denominator,
|
||||
t = crossProduct(subtractPoints(q, p), s) / denominator;
|
||||
|
||||
if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) {
|
||||
return geoInterp(p, p2, t);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
export function geoPathIntersections(path1, path2) {
|
||||
var intersections = [];
|
||||
for (var i = 0; i < path1.length - 1; i++) {
|
||||
for (var j = 0; j < path2.length - 1; j++) {
|
||||
var a = [ path1[i], path1[i+1] ],
|
||||
b = [ path2[j], path2[j+1] ],
|
||||
hit = geoLineIntersection(a, b);
|
||||
if (hit) intersections.push(hit);
|
||||
}
|
||||
}
|
||||
return intersections;
|
||||
}
|
||||
|
||||
|
||||
// Return whether point is contained in polygon.
|
||||
//
|
||||
// `point` should be a 2-item array of coordinates.
|
||||
// `polygon` should be an array of 2-item arrays of coordinates.
|
||||
//
|
||||
// From https://github.com/substack/point-in-polygon.
|
||||
// ray-casting algorithm based on
|
||||
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
|
||||
//
|
||||
export function geoPointInPolygon(point, polygon) {
|
||||
var x = point[0],
|
||||
y = point[1],
|
||||
inside = false;
|
||||
|
||||
for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||
var xi = polygon[i][0], yi = polygon[i][1];
|
||||
var xj = polygon[j][0], yj = polygon[j][1];
|
||||
|
||||
var intersect = ((yi > y) !== (yj > y)) &&
|
||||
(x < (xj - xi) * (y - yi) / (yj - yi) + xi);
|
||||
if (intersect) inside = !inside;
|
||||
}
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
|
||||
export function geoPolygonContainsPolygon(outer, inner) {
|
||||
return _.every(inner, function(point) {
|
||||
return geoPointInPolygon(point, outer);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function geoPolygonIntersectsPolygon(outer, inner, checkSegments) {
|
||||
function testSegments(outer, inner) {
|
||||
for (var i = 0; i < outer.length - 1; i++) {
|
||||
for (var j = 0; j < inner.length - 1; j++) {
|
||||
var a = [ outer[i], outer[i+1] ],
|
||||
b = [ inner[j], inner[j+1] ];
|
||||
if (geoLineIntersection(a, b)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function testPoints(outer, inner) {
|
||||
return _.some(inner, function(point) {
|
||||
return geoPointInPolygon(point, outer);
|
||||
});
|
||||
}
|
||||
|
||||
return testPoints(outer, inner) || (!!checkSegments && testSegments(outer, inner));
|
||||
}
|
||||
|
||||
|
||||
export function geoPathLength(path) {
|
||||
var length = 0;
|
||||
for (var i = 0; i < path.length - 1; i++) {
|
||||
length += geoEuclideanDistance(path[i], path[i + 1]);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
export { geoRoundCoords } from './geo.js';
|
||||
export { geoRotate } from './geo.js';
|
||||
export { geoLatToMeters } from './geo.js';
|
||||
export { geoLineIntersection } from './geo.js';
|
||||
export { geoLonToMeters } from './geo.js';
|
||||
export { geoMetersToLat } from './geo.js';
|
||||
export { geoMetersToLon } from './geo.js';
|
||||
export { geoMetersToOffset } from './geo.js';
|
||||
export { geoOffsetToMeters } from './geo.js';
|
||||
export { geoPathIntersections } from './geo.js';
|
||||
export { geoPathLength } from './geo.js';
|
||||
export { geoPointInPolygon } from './geo.js';
|
||||
export { geoPolygonContainsPolygon } from './geo.js';
|
||||
export { geoPolygonIntersectsPolygon } from './geo.js';
|
||||
export { geoSphericalDistance } from './geo.js';
|
||||
|
||||
@@ -6,6 +6,6 @@ export { modeDragNode } from './drag_node';
|
||||
export { modeDrawArea } from './draw_area';
|
||||
export { modeDrawLine } from './draw_line';
|
||||
export { modeMove } from './move';
|
||||
export { modeRotateWay } from './rotate_way';
|
||||
export { modeRotate } from './rotate';
|
||||
export { modeSave } from './save';
|
||||
export { modeSelect } from './select';
|
||||
|
||||
+75
-33
@@ -1,10 +1,24 @@
|
||||
import * as d3 from 'd3';
|
||||
import { d3keybinding } from '../lib/d3.keybinding.js';
|
||||
import { t } from '../util/locale';
|
||||
import { modeBrowse, modeSelect } from './index';
|
||||
import { actionMove, actionNoop } from '../actions/index';
|
||||
|
||||
import { actionMove } from '../actions/index';
|
||||
import { behaviorEdit } from '../behavior/index';
|
||||
|
||||
import {
|
||||
modeBrowse,
|
||||
modeSelect
|
||||
} from './index';
|
||||
|
||||
import {
|
||||
operationCircularize,
|
||||
operationDelete,
|
||||
operationOrthogonalize,
|
||||
operationReflectLong,
|
||||
operationReflectShort,
|
||||
operationRotate
|
||||
} from '../operations/index';
|
||||
|
||||
|
||||
export function modeMove(context, entityIDs, baseGraph) {
|
||||
var mode = {
|
||||
@@ -13,25 +27,66 @@ export function modeMove(context, entityIDs, baseGraph) {
|
||||
};
|
||||
|
||||
var keybinding = d3keybinding('move'),
|
||||
edit = behaviorEdit(context),
|
||||
behaviors = [
|
||||
behaviorEdit(context),
|
||||
operationCircularize(entityIDs, context).behavior,
|
||||
operationDelete(entityIDs, context).behavior,
|
||||
operationOrthogonalize(entityIDs, context).behavior,
|
||||
operationReflectLong(entityIDs, context).behavior,
|
||||
operationReflectShort(entityIDs, context).behavior,
|
||||
operationRotate(entityIDs, context).behavior
|
||||
],
|
||||
annotation = entityIDs.length === 1 ?
|
||||
t('operations.move.annotation.' + context.geometry(entityIDs[0])) :
|
||||
t('operations.move.annotation.multiple'),
|
||||
prevGraph,
|
||||
cache,
|
||||
origin,
|
||||
nudgeInterval;
|
||||
|
||||
|
||||
function vecSub(a, b) { return [a[0] - b[0], a[1] - b[1]]; }
|
||||
function vecSub(a, b) {
|
||||
return [a[0] - b[0], a[1] - b[1]];
|
||||
}
|
||||
|
||||
|
||||
function edge(point, size) {
|
||||
var pad = [30, 100, 30, 100];
|
||||
if (point[0] > size[0] - pad[0]) return [-10, 0];
|
||||
else if (point[0] < pad[2]) return [10, 0];
|
||||
else if (point[1] > size[1] - pad[1]) return [0, -10];
|
||||
else if (point[1] < pad[3]) return [0, 10];
|
||||
return null;
|
||||
var pad = [30, 100, 30, 100],
|
||||
x = 0,
|
||||
y = 0;
|
||||
|
||||
if (point[0] > size[0] - pad[0])
|
||||
x = -10;
|
||||
if (point[0] < pad[2])
|
||||
x = 10;
|
||||
if (point[1] > size[1] - pad[1])
|
||||
y = -10;
|
||||
if (point[1] < pad[3])
|
||||
y = 10;
|
||||
|
||||
if (x || y) return [x, y];
|
||||
else return null;
|
||||
}
|
||||
|
||||
|
||||
function doMove(nudge) {
|
||||
nudge = nudge || [0, 0];
|
||||
|
||||
var fn;
|
||||
if (prevGraph !== context.graph()) {
|
||||
cache = {};
|
||||
origin = context.map().mouseCoordinates();
|
||||
fn = context.perform;
|
||||
} else {
|
||||
fn = context.overwrite;
|
||||
}
|
||||
|
||||
var currMouse = context.mouse(),
|
||||
origMouse = context.projection(origin),
|
||||
delta = vecSub(vecSub(currMouse, origMouse), nudge);
|
||||
|
||||
fn(actionMove(entityIDs, delta, context.projection, cache), annotation);
|
||||
prevGraph = context.graph();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,14 +94,7 @@ export function modeMove(context, entityIDs, baseGraph) {
|
||||
if (nudgeInterval) window.clearInterval(nudgeInterval);
|
||||
nudgeInterval = window.setInterval(function() {
|
||||
context.pan(nudge);
|
||||
|
||||
var currMouse = context.mouse(),
|
||||
origMouse = context.projection(origin),
|
||||
delta = vecSub(vecSub(currMouse, origMouse), nudge),
|
||||
action = actionMove(entityIDs, delta, context.projection, cache);
|
||||
|
||||
context.overwrite(action, annotation);
|
||||
|
||||
doMove(nudge);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
@@ -58,14 +106,8 @@ export function modeMove(context, entityIDs, baseGraph) {
|
||||
|
||||
|
||||
function move() {
|
||||
var currMouse = context.mouse(),
|
||||
origMouse = context.projection(origin),
|
||||
delta = vecSub(currMouse, origMouse),
|
||||
action = actionMove(entityIDs, delta, context.projection, cache);
|
||||
|
||||
context.overwrite(action, annotation);
|
||||
|
||||
var nudge = edge(currMouse, context.map().dimensions());
|
||||
doMove();
|
||||
var nudge = edge(context.mouse(), context.map().dimensions());
|
||||
if (nudge) startNudge(nudge);
|
||||
else stopNudge();
|
||||
}
|
||||
@@ -97,14 +139,12 @@ export function modeMove(context, entityIDs, baseGraph) {
|
||||
|
||||
mode.enter = function() {
|
||||
origin = context.map().mouseCoordinates();
|
||||
prevGraph = null;
|
||||
cache = {};
|
||||
|
||||
context.install(edit);
|
||||
|
||||
context.perform(
|
||||
actionNoop(),
|
||||
annotation
|
||||
);
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.install(behavior);
|
||||
});
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.move', move)
|
||||
@@ -125,7 +165,9 @@ export function modeMove(context, entityIDs, baseGraph) {
|
||||
mode.exit = function() {
|
||||
stopNudge();
|
||||
|
||||
context.uninstall(edit);
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.uninstall(behavior);
|
||||
});
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.move', null)
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import * as d3 from 'd3';
|
||||
import { d3keybinding } from '../lib/d3.keybinding.js';
|
||||
import { t } from '../util/locale';
|
||||
|
||||
import { actionRotate } from '../actions/index';
|
||||
import { behaviorEdit } from '../behavior/index';
|
||||
|
||||
import {
|
||||
modeBrowse,
|
||||
modeSelect
|
||||
} from './index';
|
||||
|
||||
import {
|
||||
operationCircularize,
|
||||
operationDelete,
|
||||
operationMove,
|
||||
operationOrthogonalize,
|
||||
operationReflectLong,
|
||||
operationReflectShort
|
||||
} from '../operations/index';
|
||||
|
||||
import {
|
||||
polygonHull as d3polygonHull,
|
||||
polygonCentroid as d3polygonCentroid
|
||||
} from 'd3';
|
||||
|
||||
import { utilGetAllNodes } from '../util';
|
||||
|
||||
|
||||
export function modeRotate(context, entityIDs) {
|
||||
var mode = {
|
||||
id: 'rotate',
|
||||
button: 'browse'
|
||||
};
|
||||
|
||||
var keybinding = d3keybinding('rotate'),
|
||||
behaviors = [
|
||||
behaviorEdit(context),
|
||||
operationCircularize(entityIDs, context).behavior,
|
||||
operationDelete(entityIDs, context).behavior,
|
||||
operationMove(entityIDs, context).behavior,
|
||||
operationOrthogonalize(entityIDs, context).behavior,
|
||||
operationReflectLong(entityIDs, context).behavior,
|
||||
operationReflectShort(entityIDs, context).behavior
|
||||
],
|
||||
annotation = entityIDs.length === 1 ?
|
||||
t('operations.rotate.annotation.' + context.geometry(entityIDs[0])) :
|
||||
t('operations.rotate.annotation.multiple'),
|
||||
prevGraph,
|
||||
prevAngle,
|
||||
prevTransform,
|
||||
pivot;
|
||||
|
||||
|
||||
function doRotate() {
|
||||
var fn;
|
||||
if (context.graph() !== prevGraph) {
|
||||
fn = context.perform;
|
||||
} else {
|
||||
fn = context.replace;
|
||||
}
|
||||
|
||||
// projection changed, recalculate pivot
|
||||
var projection = context.projection;
|
||||
var currTransform = projection.transform();
|
||||
if (!prevTransform ||
|
||||
currTransform.k !== prevTransform.k ||
|
||||
currTransform.x !== prevTransform.x ||
|
||||
currTransform.y !== prevTransform.y) {
|
||||
|
||||
var nodes = utilGetAllNodes(entityIDs, context.graph()),
|
||||
points = nodes.map(function(n) { return projection(n.loc); });
|
||||
|
||||
pivot = d3polygonCentroid(d3polygonHull(points));
|
||||
prevAngle = undefined;
|
||||
}
|
||||
|
||||
|
||||
var currMouse = context.mouse(),
|
||||
currAngle = Math.atan2(currMouse[1] - pivot[1], currMouse[0] - pivot[0]);
|
||||
|
||||
if (typeof prevAngle === 'undefined') prevAngle = currAngle;
|
||||
var delta = currAngle - prevAngle;
|
||||
|
||||
fn(actionRotate(entityIDs, pivot, delta, projection), annotation);
|
||||
|
||||
prevTransform = currTransform;
|
||||
prevAngle = currAngle;
|
||||
prevGraph = context.graph();
|
||||
}
|
||||
|
||||
|
||||
function finish() {
|
||||
d3.event.stopPropagation();
|
||||
context.enter(modeSelect(context, entityIDs).suppressMenu(true));
|
||||
}
|
||||
|
||||
|
||||
function cancel() {
|
||||
context.pop();
|
||||
context.enter(modeSelect(context, entityIDs).suppressMenu(true));
|
||||
}
|
||||
|
||||
|
||||
function undone() {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.install(behavior);
|
||||
});
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.rotate', doRotate)
|
||||
.on('click.rotate', finish);
|
||||
|
||||
context.history()
|
||||
.on('undone.rotate', undone);
|
||||
|
||||
keybinding
|
||||
.on('⎋', cancel)
|
||||
.on('↩', finish);
|
||||
|
||||
d3.select(document)
|
||||
.call(keybinding);
|
||||
};
|
||||
|
||||
|
||||
mode.exit = function() {
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.uninstall(behavior);
|
||||
});
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.rotate', null)
|
||||
.on('click.rotate', null);
|
||||
|
||||
context.history()
|
||||
.on('undone.rotate', null);
|
||||
|
||||
keybinding.off();
|
||||
};
|
||||
|
||||
|
||||
return mode;
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import * as d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import { d3keybinding } from '../lib/d3.keybinding.js';
|
||||
import { t } from '../util/locale';
|
||||
import { modeBrowse, modeSelect } from './index';
|
||||
import { actionNoop, actionRotateWay } from '../actions/index';
|
||||
import { behaviorEdit } from '../behavior/index';
|
||||
|
||||
|
||||
export function modeRotateWay(context, wayId) {
|
||||
var mode = {
|
||||
id: 'rotate-way',
|
||||
button: 'browse'
|
||||
};
|
||||
|
||||
var keybinding = d3keybinding('rotate-way'),
|
||||
edit = behaviorEdit(context);
|
||||
|
||||
|
||||
mode.enter = function() {
|
||||
context.install(edit);
|
||||
|
||||
var annotation = t('operations.rotate.annotation.' + context.geometry(wayId)),
|
||||
way = context.graph().entity(wayId),
|
||||
nodes = _.uniq(context.graph().childNodes(way)),
|
||||
points = nodes.map(function(n) { return context.projection(n.loc); }),
|
||||
pivot = d3.polygonCentroid(points),
|
||||
angle;
|
||||
|
||||
context.perform(
|
||||
actionNoop(),
|
||||
annotation
|
||||
);
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.rotate-way', rotate)
|
||||
.on('click.rotate-way', finish);
|
||||
|
||||
context.history()
|
||||
.on('undone.rotate-way', undone);
|
||||
|
||||
keybinding
|
||||
.on('⎋', cancel)
|
||||
.on('↩', finish);
|
||||
|
||||
d3.select(document)
|
||||
.call(keybinding);
|
||||
|
||||
|
||||
function rotate() {
|
||||
var mousePoint = context.mouse(),
|
||||
newAngle = Math.atan2(mousePoint[1] - pivot[1], mousePoint[0] - pivot[0]);
|
||||
|
||||
if (typeof angle === 'undefined') angle = newAngle;
|
||||
|
||||
context.replace(
|
||||
actionRotateWay(wayId, pivot, newAngle - angle, context.projection),
|
||||
annotation
|
||||
);
|
||||
|
||||
angle = newAngle;
|
||||
}
|
||||
|
||||
|
||||
function finish() {
|
||||
d3.event.stopPropagation();
|
||||
context.enter(modeSelect(context, [wayId]).suppressMenu(true));
|
||||
}
|
||||
|
||||
|
||||
function cancel() {
|
||||
context.pop();
|
||||
context.enter(modeSelect(context, [wayId]).suppressMenu(true));
|
||||
}
|
||||
|
||||
|
||||
function undone() {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
mode.exit = function() {
|
||||
context.uninstall(edit);
|
||||
|
||||
context.surface()
|
||||
.on('mousemove.rotate-way', null)
|
||||
.on('click.rotate-way', null);
|
||||
|
||||
context.history()
|
||||
.on('undone.rotate-way', null);
|
||||
|
||||
keybinding.off();
|
||||
};
|
||||
|
||||
|
||||
return mode;
|
||||
}
|
||||
+10
-15
@@ -392,16 +392,22 @@ export function modeSelect(context, selectedIDs) {
|
||||
|
||||
if (!checkSelectedIDs()) return;
|
||||
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.install(behavior);
|
||||
});
|
||||
|
||||
var operations = _.without(d3.values(Operations), Operations.operationDelete)
|
||||
.map(function(o) { return o(selectedIDs, context); })
|
||||
.filter(function(o) { return o.available(); });
|
||||
|
||||
operations.unshift(Operations.operationDelete(selectedIDs, context));
|
||||
|
||||
operations.forEach(function(operation) {
|
||||
if (operation.behavior) {
|
||||
behaviors.push(operation.behavior);
|
||||
}
|
||||
});
|
||||
|
||||
behaviors.forEach(function(behavior) {
|
||||
context.install(behavior);
|
||||
});
|
||||
|
||||
keybinding
|
||||
.on(['[','pgup'], previousVertex)
|
||||
.on([']', 'pgdown'], nextVertex)
|
||||
@@ -411,17 +417,6 @@ export function modeSelect(context, selectedIDs) {
|
||||
.on('⎋', esc, true)
|
||||
.on('space', toggleMenu);
|
||||
|
||||
operations.forEach(function(operation) {
|
||||
operation.keys.forEach(function(key) {
|
||||
keybinding.on(key, function() {
|
||||
d3.event.preventDefault();
|
||||
if (!(context.inIntro() || operation.disabled())) {
|
||||
operation();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
d3.select(document)
|
||||
.call(keybinding);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { t } from '../util/locale';
|
||||
import { actionCircularize } from '../actions/index';
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
|
||||
|
||||
export function operationCircularize(selectedIDs, context) {
|
||||
@@ -10,9 +11,9 @@ export function operationCircularize(selectedIDs, context) {
|
||||
geometry = context.geometry(entityId),
|
||||
action = actionCircularize(entityId, context.projection);
|
||||
|
||||
|
||||
var operation = function() {
|
||||
var annotation = t('operations.circularize.annotation.' + geometry);
|
||||
context.perform(action, annotation);
|
||||
context.perform(action, t('operations.circularize.annotation.' + geometry));
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +46,7 @@ export function operationCircularize(selectedIDs, context) {
|
||||
operation.id = 'circularize';
|
||||
operation.keys = [t('operations.circularize.key')];
|
||||
operation.title = t('operations.circularize.title');
|
||||
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { t } from '../util/locale';
|
||||
import { modeDrawLine } from '../modes/index';
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
|
||||
|
||||
export function operationContinue(selectedIDs, context) {
|
||||
@@ -54,7 +55,7 @@ export function operationContinue(selectedIDs, context) {
|
||||
operation.id = 'continue';
|
||||
operation.keys = [t('operations.continue.key')];
|
||||
operation.title = t('operations.continue.title');
|
||||
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import _ from 'lodash';
|
||||
import { t } from '../util/locale';
|
||||
import { modeBrowse, modeSelect } from '../modes/index';
|
||||
import { actionDeleteMultiple } from '../actions/index';
|
||||
import { uiCmd } from '../ui/index';
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
import { geoSphericalDistance } from '../geo/index';
|
||||
import { modeBrowse, modeSelect } from '../modes/index';
|
||||
import { uiCmd } from '../ui/index';
|
||||
|
||||
|
||||
export function operationDelete(selectedIDs, context) {
|
||||
var action = actionDeleteMultiple(selectedIDs);
|
||||
var multi = (selectedIDs.length === 1 ? 'single' : 'multiple'),
|
||||
action = actionDeleteMultiple(selectedIDs);
|
||||
|
||||
|
||||
var operation = function() {
|
||||
var annotation,
|
||||
@@ -66,23 +69,49 @@ export function operationDelete(selectedIDs, context) {
|
||||
var reason;
|
||||
if (_.some(selectedIDs, context.hasHiddenConnections)) {
|
||||
reason = 'connected_to_hidden';
|
||||
} else if (_.some(selectedIDs, protectedMember)) {
|
||||
reason = 'part_of_relation';
|
||||
} else if (_.some(selectedIDs, incompleteRelation)) {
|
||||
reason = 'incomplete_relation';
|
||||
}
|
||||
return action.disabled(context.graph()) || reason;
|
||||
return reason;
|
||||
|
||||
function incompleteRelation(id) {
|
||||
var entity = context.entity(id);
|
||||
return entity.type === 'relation' && !entity.isComplete(context.graph());
|
||||
}
|
||||
|
||||
function protectedMember(id) {
|
||||
var entity = context.entity(id);
|
||||
if (entity.type !== 'way') return false;
|
||||
|
||||
var parents = context.graph().parentRelations(entity);
|
||||
for (var i = 0; i < parents.length; i++) {
|
||||
var parent = parents[i],
|
||||
type = parent.tags.type,
|
||||
role = parent.memberById(id).role || 'outer';
|
||||
if (type === 'route' || type === 'boundary' || (type === 'multipolygon' && role === 'outer')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
operation.tooltip = function() {
|
||||
var disable = operation.disabled();
|
||||
return disable ?
|
||||
t('operations.delete.' + disable) :
|
||||
t('operations.delete.description');
|
||||
t('operations.delete.' + disable + '.' + multi) :
|
||||
t('operations.delete.description' + '.' + multi);
|
||||
};
|
||||
|
||||
|
||||
operation.id = 'delete';
|
||||
operation.keys = [uiCmd('⌘⌫'), uiCmd('⌘⌦'), uiCmd('⌦')];
|
||||
operation.title = t('operations.delete.title');
|
||||
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { t } from '../util/locale';
|
||||
import { actionDisconnect } from '../actions/index';
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
|
||||
|
||||
export function operationDisconnect(selectedIDs, context) {
|
||||
@@ -15,6 +16,7 @@ export function operationDisconnect(selectedIDs, context) {
|
||||
action.limitWays(_.without(selectedIDs, entityId));
|
||||
}
|
||||
|
||||
|
||||
var operation = function() {
|
||||
context.perform(action, t('operations.disconnect.annotation'));
|
||||
};
|
||||
@@ -45,7 +47,7 @@ export function operationDisconnect(selectedIDs, context) {
|
||||
operation.id = 'disconnect';
|
||||
operation.keys = [t('operations.disconnect.key')];
|
||||
operation.title = t('operations.disconnect.title');
|
||||
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
actionMergePolygon
|
||||
} from '../actions/index';
|
||||
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
import { modeSelect } from '../modes/index';
|
||||
|
||||
|
||||
@@ -68,7 +69,7 @@ export function operationMerge(selectedIDs, context) {
|
||||
operation.id = 'merge';
|
||||
operation.keys = [t('operations.merge.key')];
|
||||
operation.title = t('operations.merge.title');
|
||||
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import _ from 'lodash';
|
||||
import { t } from '../util/locale';
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
import { geoExtent } from '../geo/index';
|
||||
import { actionMove } from '../actions/index';
|
||||
import { modeMove } from '../modes/index';
|
||||
|
||||
|
||||
export function operationMove(selectedIDs, context) {
|
||||
var extent = selectedIDs.reduce(function(extent, id) {
|
||||
var multi = (selectedIDs.length === 1 ? 'single' : 'multiple'),
|
||||
extent = selectedIDs.reduce(function(extent, id) {
|
||||
return extent.extend(context.entity(id).extent(context.graph()));
|
||||
}, geoExtent());
|
||||
|
||||
|
||||
var operation = function() {
|
||||
context.enter(modeMove(context, selectedIDs));
|
||||
};
|
||||
@@ -27,24 +29,30 @@ export function operationMove(selectedIDs, context) {
|
||||
reason = 'too_large';
|
||||
} else if (_.some(selectedIDs, context.hasHiddenConnections)) {
|
||||
reason = 'connected_to_hidden';
|
||||
} else if (_.some(selectedIDs, incompleteRelation)) {
|
||||
reason = 'incomplete_relation';
|
||||
}
|
||||
return reason;
|
||||
|
||||
return actionMove(selectedIDs).disabled(context.graph()) || reason;
|
||||
function incompleteRelation(id) {
|
||||
var entity = context.entity(id);
|
||||
return entity.type === 'relation' && !entity.isComplete(context.graph());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
operation.tooltip = function() {
|
||||
var disable = operation.disabled();
|
||||
return disable ?
|
||||
t('operations.move.' + disable) :
|
||||
t('operations.move.description');
|
||||
t('operations.move.' + disable + '.' + multi) :
|
||||
t('operations.move.description.' + multi);
|
||||
};
|
||||
|
||||
|
||||
operation.id = 'move';
|
||||
operation.keys = [t('operations.move.key')];
|
||||
operation.title = t('operations.move.title');
|
||||
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { t } from '../util/locale';
|
||||
import { actionOrthogonalize } from '../actions/index';
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
|
||||
|
||||
export function operationOrthogonalize(selectedIDs, context) {
|
||||
@@ -10,9 +11,9 @@ export function operationOrthogonalize(selectedIDs, context) {
|
||||
geometry = context.geometry(entityId),
|
||||
action = actionOrthogonalize(entityId, context.projection);
|
||||
|
||||
|
||||
var operation = function() {
|
||||
var annotation = t('operations.orthogonalize.annotation.' + geometry);
|
||||
context.perform(action, annotation);
|
||||
context.perform(action, t('operations.orthogonalize.annotation.' + geometry));
|
||||
};
|
||||
|
||||
|
||||
@@ -46,7 +47,7 @@ export function operationOrthogonalize(selectedIDs, context) {
|
||||
operation.id = 'orthogonalize';
|
||||
operation.keys = [t('operations.orthogonalize.key')];
|
||||
operation.title = t('operations.orthogonalize.title');
|
||||
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import { t } from '../util/locale';
|
||||
import { actionReflect } from '../actions/index';
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
import { geoExtent } from '../geo/index';
|
||||
|
||||
|
||||
export function operationReflectShort(selectedIDs, context) {
|
||||
@@ -14,45 +17,60 @@ export function operationReflectLong(selectedIDs, context) {
|
||||
|
||||
export function operationReflect(selectedIDs, context, axis) {
|
||||
axis = axis || 'long';
|
||||
var entityId = selectedIDs[0];
|
||||
var entity = context.entity(entityId);
|
||||
var extent = entity.extent(context.graph());
|
||||
var action = actionReflect(entityId, context.projection)
|
||||
.useLongAxis(Boolean(axis === 'long'));
|
||||
var multi = (selectedIDs.length === 1 ? 'single' : 'multiple'),
|
||||
extent = selectedIDs.reduce(function(extent, id) {
|
||||
return extent.extend(context.entity(id).extent(context.graph()));
|
||||
}, geoExtent());
|
||||
|
||||
|
||||
var operation = function() {
|
||||
context.perform(
|
||||
action,
|
||||
t('operations.reflect.annotation.' + axis)
|
||||
);
|
||||
var action = actionReflect(selectedIDs, context.projection)
|
||||
.useLongAxis(Boolean(axis === 'long'));
|
||||
context.perform(action, t('operations.reflect.annotation.' + axis + '.' + multi));
|
||||
};
|
||||
|
||||
|
||||
operation.available = function() {
|
||||
return selectedIDs.length === 1 &&
|
||||
context.geometry(entityId) === 'area';
|
||||
};
|
||||
return _.some(selectedIDs, hasArea);
|
||||
|
||||
operation.disabled = function() {
|
||||
if (extent.percentContainedIn(context.extent()) < 0.8) {
|
||||
return 'too_large';
|
||||
} else if (context.hasHiddenConnections(entityId)) {
|
||||
return 'connected_to_hidden';
|
||||
} else {
|
||||
return false;
|
||||
function hasArea(id) {
|
||||
var entity = context.entity(id);
|
||||
return (entity.type === 'way' && entity.isClosed()) ||
|
||||
(entity.type ==='relation' && entity.isMultipolygon());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
operation.disabled = function() {
|
||||
var reason;
|
||||
if (extent.area() && extent.percentContainedIn(context.extent()) < 0.8) {
|
||||
reason = 'too_large';
|
||||
} else if (_.some(selectedIDs, context.hasHiddenConnections)) {
|
||||
reason = 'connected_to_hidden';
|
||||
} else if (_.some(selectedIDs, incompleteRelation)) {
|
||||
reason = 'incomplete_relation';
|
||||
}
|
||||
return reason;
|
||||
|
||||
function incompleteRelation(id) {
|
||||
var entity = context.entity(id);
|
||||
return entity.type === 'relation' && !entity.isComplete(context.graph());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
operation.tooltip = function() {
|
||||
var disable = operation.disabled();
|
||||
return disable ?
|
||||
t('operations.reflect.' + disable) :
|
||||
t('operations.reflect.description.' + axis);
|
||||
t('operations.reflect.' + disable + '.' + multi) :
|
||||
t('operations.reflect.description.' + axis + '.' + multi);
|
||||
};
|
||||
|
||||
|
||||
operation.id = 'reflect-' + axis;
|
||||
operation.keys = [t('operations.reflect.key.' + axis)];
|
||||
operation.title = t('operations.reflect.title');
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import { t } from '../util/locale';
|
||||
import { actionReverse } from '../actions/index';
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
|
||||
|
||||
export function operationReverse(selectedIDs, context) {
|
||||
var entityId = selectedIDs[0];
|
||||
|
||||
var operation = function() {
|
||||
context.perform(
|
||||
actionReverse(entityId),
|
||||
t('operations.reverse.annotation')
|
||||
);
|
||||
context.perform(actionReverse(entityId), t('operations.reverse.annotation'));
|
||||
};
|
||||
|
||||
|
||||
operation.available = function() {
|
||||
return selectedIDs.length === 1 &&
|
||||
context.geometry(entityId) === 'line';
|
||||
return selectedIDs.length === 1 && context.geometry(entityId) === 'line';
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +29,7 @@ export function operationReverse(selectedIDs, context) {
|
||||
operation.id = 'reverse';
|
||||
operation.keys = [t('operations.reverse.key')];
|
||||
operation.title = t('operations.reverse.title');
|
||||
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
import _ from 'lodash';
|
||||
import { t } from '../util/locale';
|
||||
import { modeRotateWay } from '../modes/index';
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
import { geoExtent } from '../geo/index';
|
||||
import { modeRotate } from '../modes/index';
|
||||
|
||||
|
||||
export function operationRotate(selectedIDs, context) {
|
||||
var entityId = selectedIDs[0],
|
||||
entity = context.entity(entityId),
|
||||
extent = entity.extent(context.graph()),
|
||||
geometry = context.geometry(entityId);
|
||||
var multi = (selectedIDs.length === 1 ? 'single' : 'multiple'),
|
||||
extent = selectedIDs.reduce(function(extent, id) {
|
||||
return extent.extend(context.entity(id).extent(context.graph()));
|
||||
}, geoExtent());
|
||||
|
||||
|
||||
var operation = function() {
|
||||
context.enter(modeRotateWay(context, entityId));
|
||||
context.enter(modeRotate(context, selectedIDs));
|
||||
};
|
||||
|
||||
|
||||
operation.available = function() {
|
||||
if (selectedIDs.length !== 1 || entity.type !== 'way')
|
||||
return false;
|
||||
if (geometry === 'area')
|
||||
return true;
|
||||
if (entity.isClosed() &&
|
||||
context.graph().parentRelations(entity).some(function(r) { return r.isMultipolygon(); }))
|
||||
return true;
|
||||
return false;
|
||||
return _.some(selectedIDs, hasArea);
|
||||
|
||||
function hasArea(id) {
|
||||
var entity = context.entity(id);
|
||||
return (entity.type === 'way' && entity.isClosed()) ||
|
||||
(entity.type ==='relation' && entity.isMultipolygon());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
operation.disabled = function() {
|
||||
if (extent.percentContainedIn(context.extent()) < 0.8) {
|
||||
return 'too_large';
|
||||
} else if (context.hasHiddenConnections(entityId)) {
|
||||
return 'connected_to_hidden';
|
||||
} else {
|
||||
return false;
|
||||
var reason;
|
||||
if (extent.area() && extent.percentContainedIn(context.extent()) < 0.8) {
|
||||
reason = 'too_large';
|
||||
} else if (_.some(selectedIDs, context.hasHiddenConnections)) {
|
||||
reason = 'connected_to_hidden';
|
||||
} else if (_.some(selectedIDs, incompleteRelation)) {
|
||||
reason = 'incomplete_relation';
|
||||
}
|
||||
return reason;
|
||||
|
||||
function incompleteRelation(id) {
|
||||
var entity = context.entity(id);
|
||||
return entity.type === 'relation' && !entity.isComplete(context.graph());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,15 +49,15 @@ export function operationRotate(selectedIDs, context) {
|
||||
operation.tooltip = function() {
|
||||
var disable = operation.disabled();
|
||||
return disable ?
|
||||
t('operations.rotate.' + disable) :
|
||||
t('operations.rotate.description');
|
||||
t('operations.rotate.' + disable + '.' + multi) :
|
||||
t('operations.rotate.description.' + multi);
|
||||
};
|
||||
|
||||
|
||||
operation.id = 'rotate';
|
||||
operation.keys = [t('operations.rotate.key')];
|
||||
operation.title = t('operations.rotate.title');
|
||||
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import { t } from '../util/locale';
|
||||
import { modeSelect } from '../modes/index';
|
||||
import { actionSplit } from '../actions/index';
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
import { modeSelect } from '../modes/index';
|
||||
|
||||
|
||||
export function operationSplit(selectedIDs, context) {
|
||||
@@ -64,7 +65,7 @@ export function operationSplit(selectedIDs, context) {
|
||||
operation.id = 'split';
|
||||
operation.keys = [t('operations.split.key')];
|
||||
operation.title = t('operations.split.title');
|
||||
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { t } from '../util/locale';
|
||||
import { actionStraighten } from '../actions/index';
|
||||
import { behaviorOperation } from '../behavior/index';
|
||||
|
||||
|
||||
export function operationStraighten(selectedIDs, context) {
|
||||
@@ -9,8 +10,7 @@ export function operationStraighten(selectedIDs, context) {
|
||||
|
||||
|
||||
function operation() {
|
||||
var annotation = t('operations.straighten.annotation');
|
||||
context.perform(action, annotation);
|
||||
context.perform(action, t('operations.straighten.annotation'));
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export function operationStraighten(selectedIDs, context) {
|
||||
operation.id = 'straighten';
|
||||
operation.keys = [t('operations.straighten.key')];
|
||||
operation.title = t('operations.straighten.title');
|
||||
|
||||
operation.behavior = behaviorOperation(context).which(operation);
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export { utilTagText } from './util';
|
||||
export { utilEntitySelector } from './util';
|
||||
export { utilEntityOrMemberSelector } from './util';
|
||||
export { utilGetAllNodes } from './util';
|
||||
export { utilDisplayName } from './util';
|
||||
export { utilDisplayType } from './util';
|
||||
export { utilStringQs } from './util';
|
||||
|
||||
@@ -32,6 +32,30 @@ export function utilEntityOrMemberSelector(ids, graph) {
|
||||
}
|
||||
|
||||
|
||||
export function utilGetAllNodes(ids, graph) {
|
||||
var seen = {};
|
||||
var nodes = [];
|
||||
ids.forEach(getNodes);
|
||||
return nodes;
|
||||
|
||||
function getNodes(id) {
|
||||
if (seen[id]) return;
|
||||
seen[id] = true;
|
||||
|
||||
var entity = graph.hasEntity(id);
|
||||
if (!entity) return;
|
||||
|
||||
if (entity.type === 'node') {
|
||||
nodes.push(entity);
|
||||
} else if (entity.type === 'way') {
|
||||
entity.nodes.forEach(getNodes);
|
||||
} else {
|
||||
entity.members.map(function(member) { return member.id; }).forEach(getNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function utilDisplayName(entity) {
|
||||
var localizedNameKey = 'name:' + utilDetect().locale.toLowerCase().split('-')[0],
|
||||
name = entity.tags[localizedNameKey] || entity.tags.name || '',
|
||||
|
||||
@@ -19,13 +19,14 @@ describe('iD.actionDeleteMultiple', function () {
|
||||
expect(graph.hasEntity(n.id)).to.be.undefined;
|
||||
});
|
||||
|
||||
describe('#disabled', function () {
|
||||
it('returns the result of the first action that is disabled', function () {
|
||||
var node = iD.Node(),
|
||||
relation = iD.Relation({members: [{id: 'w'}]}),
|
||||
graph = iD.Graph([node, relation]),
|
||||
action = iD.actionDeleteMultiple([node.id, relation.id]);
|
||||
expect(action.disabled(graph)).to.equal('incomplete_relation');
|
||||
});
|
||||
});
|
||||
// This was moved to operationDelete. We should test operations and move this test there.
|
||||
// describe('#disabled', function () {
|
||||
// it('returns the result of the first action that is disabled', function () {
|
||||
// var node = iD.Node(),
|
||||
// relation = iD.Relation({members: [{id: 'w'}]}),
|
||||
// graph = iD.Graph([node, relation]),
|
||||
// action = iD.actionDeleteMultiple([node.id, relation.id]);
|
||||
// expect(action.disabled(graph)).to.equal('incomplete_relation');
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
@@ -82,12 +82,13 @@ describe('iD.actionDeleteRelation', function () {
|
||||
expect(graph.hasEntity(parent.id)).to.be.undefined;
|
||||
});
|
||||
|
||||
describe('#disabled', function() {
|
||||
it('returns \'incomplete_relation\' if the relation is incomplete', function() {
|
||||
var relation = iD.Relation({members: [{id: 'w'}]}),
|
||||
graph = iD.Graph([relation]),
|
||||
action = iD.actionDeleteRelation(relation.id);
|
||||
expect(action.disabled(graph)).to.equal('incomplete_relation');
|
||||
});
|
||||
});
|
||||
// This was moved to operationDelete. We should test operations and move this test there.
|
||||
// describe('#disabled', function() {
|
||||
// it('returns \'incomplete_relation\' if the relation is incomplete', function() {
|
||||
// var relation = iD.Relation({members: [{id: 'w'}]}),
|
||||
// graph = iD.Graph([relation]),
|
||||
// action = iD.actionDeleteRelation(relation.id);
|
||||
// expect(action.disabled(graph)).to.equal('incomplete_relation');
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
@@ -69,31 +69,32 @@ describe('iD.actionDeleteWay', function() {
|
||||
expect(graph.hasEntity(relation.id)).to.be.undefined;
|
||||
});
|
||||
|
||||
describe('#disabled', function () {
|
||||
it('returns \'part_of_relation\' for members of route and boundary relations', function () {
|
||||
var a = iD.Way({id: 'a'}),
|
||||
b = iD.Way({id: 'b'}),
|
||||
route = iD.Relation({members: [{id: 'a'}], tags: {type: 'route'}}),
|
||||
boundary = iD.Relation({members: [{id: 'b'}], tags: {type: 'boundary'}}),
|
||||
graph = iD.Graph([a, b, route, boundary]);
|
||||
expect(iD.actionDeleteWay('a').disabled(graph)).to.equal('part_of_relation');
|
||||
expect(iD.actionDeleteWay('b').disabled(graph)).to.equal('part_of_relation');
|
||||
});
|
||||
// This was moved to operationDelete. We should test operations and move this test there.
|
||||
// describe('#disabled', function () {
|
||||
// it('returns \'part_of_relation\' for members of route and boundary relations', function () {
|
||||
// var a = iD.Way({id: 'a'}),
|
||||
// b = iD.Way({id: 'b'}),
|
||||
// route = iD.Relation({members: [{id: 'a'}], tags: {type: 'route'}}),
|
||||
// boundary = iD.Relation({members: [{id: 'b'}], tags: {type: 'boundary'}}),
|
||||
// graph = iD.Graph([a, b, route, boundary]);
|
||||
// expect(iD.actionDeleteWay('a').disabled(graph)).to.equal('part_of_relation');
|
||||
// expect(iD.actionDeleteWay('b').disabled(graph)).to.equal('part_of_relation');
|
||||
// });
|
||||
|
||||
it('returns \'part_of_relation\' for outer members of multipolygons', function () {
|
||||
var way = iD.Way({id: 'w'}),
|
||||
relation = iD.Relation({members: [{id: 'w', role: 'outer'}], tags: {type: 'multipolygon'}}),
|
||||
graph = iD.Graph([way, relation]),
|
||||
action = iD.actionDeleteWay(way.id);
|
||||
expect(action.disabled(graph)).to.equal('part_of_relation');
|
||||
});
|
||||
// it('returns \'part_of_relation\' for outer members of multipolygons', function () {
|
||||
// var way = iD.Way({id: 'w'}),
|
||||
// relation = iD.Relation({members: [{id: 'w', role: 'outer'}], tags: {type: 'multipolygon'}}),
|
||||
// graph = iD.Graph([way, relation]),
|
||||
// action = iD.actionDeleteWay(way.id);
|
||||
// expect(action.disabled(graph)).to.equal('part_of_relation');
|
||||
// });
|
||||
|
||||
it('returns falsy for inner members of multipolygons', function () {
|
||||
var way = iD.Way({id: 'w'}),
|
||||
relation = iD.Relation({members: [{id: 'w', role: 'inner'}], tags: {type: 'multipolygon'}}),
|
||||
graph = iD.Graph([way, relation]),
|
||||
action = iD.actionDeleteWay(way.id);
|
||||
expect(action.disabled(graph)).not.ok;
|
||||
});
|
||||
});
|
||||
// it('returns falsy for inner members of multipolygons', function () {
|
||||
// var way = iD.Way({id: 'w'}),
|
||||
// relation = iD.Relation({members: [{id: 'w', role: 'inner'}], tags: {type: 'multipolygon'}}),
|
||||
// graph = iD.Graph([way, relation]),
|
||||
// action = iD.actionDeleteWay(way.id);
|
||||
// expect(action.disabled(graph)).not.ok;
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
+22
-21
@@ -1,29 +1,30 @@
|
||||
describe('iD.actionMove', function() {
|
||||
var projection = d3.geoMercator().scale(250 / Math.PI);
|
||||
|
||||
describe('#disabled', function() {
|
||||
it('returns falsy by default', function() {
|
||||
var node = iD.Node({loc: [0, 0]}),
|
||||
action = iD.actionMove([node.id], [0, 0], projection),
|
||||
graph = iD.Graph([node]);
|
||||
expect(action.disabled(graph)).not.to.be.ok;
|
||||
});
|
||||
// This was moved to operationMove. We should test operations and move this test there.
|
||||
// describe('#disabled', function() {
|
||||
// it('returns falsy by default', function() {
|
||||
// var node = iD.Node({loc: [0, 0]}),
|
||||
// action = iD.actionMove([node.id], [0, 0], projection),
|
||||
// graph = iD.Graph([node]);
|
||||
// expect(action.disabled(graph)).not.to.be.ok;
|
||||
// });
|
||||
|
||||
it('returns \'incomplete_relation\' for an incomplete relation', function() {
|
||||
var relation = iD.Relation({members: [{id: 1}]}),
|
||||
action = iD.actionMove([relation.id], [0, 0], projection),
|
||||
graph = iD.Graph([relation]);
|
||||
expect(action.disabled(graph)).to.equal('incomplete_relation');
|
||||
});
|
||||
// it('returns \'incomplete_relation\' for an incomplete relation', function() {
|
||||
// var relation = iD.Relation({members: [{id: 1}]}),
|
||||
// action = iD.actionMove([relation.id], [0, 0], projection),
|
||||
// graph = iD.Graph([relation]);
|
||||
// expect(action.disabled(graph)).to.equal('incomplete_relation');
|
||||
// });
|
||||
|
||||
it('returns falsy for a complete relation', function() {
|
||||
var node = iD.Node({loc: [0, 0]}),
|
||||
relation = iD.Relation({members: [{id: node.id}]}),
|
||||
action = iD.actionMove([relation.id], [0, 0], projection),
|
||||
graph = iD.Graph([node, relation]);
|
||||
expect(action.disabled(graph)).not.to.be.ok;
|
||||
});
|
||||
});
|
||||
// it('returns falsy for a complete relation', function() {
|
||||
// var node = iD.Node({loc: [0, 0]}),
|
||||
// relation = iD.Relation({members: [{id: node.id}]}),
|
||||
// action = iD.actionMove([relation.id], [0, 0], projection),
|
||||
// graph = iD.Graph([node, relation]);
|
||||
// expect(action.disabled(graph)).not.to.be.ok;
|
||||
// });
|
||||
// });
|
||||
|
||||
it('moves all nodes in a way by the given amount', function() {
|
||||
var node1 = iD.Node({loc: [0, 0]}),
|
||||
|
||||
@@ -2,19 +2,6 @@ describe('iD.actionReflect', function() {
|
||||
var projection = d3.geoMercator();
|
||||
|
||||
it('does not create or remove nodes', function () {
|
||||
var graph = iD.Graph([
|
||||
iD.Node({id: 'a', loc: [0, 0]}),
|
||||
iD.Node({id: 'b', loc: [4, 0]}),
|
||||
iD.Node({id: 'c', loc: [4, 2]}),
|
||||
iD.Node({id: 'd', loc: [1, 2]}),
|
||||
iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a'], tags: { area: 'yes'}})
|
||||
]);
|
||||
graph = iD.actionReflect('-', projection)(graph);
|
||||
expect(graph.entity('-').nodes).to.have.length(5);
|
||||
});
|
||||
|
||||
|
||||
it('only operates on areas', function () {
|
||||
var graph = iD.Graph([
|
||||
iD.Node({id: 'a', loc: [0, 0]}),
|
||||
iD.Node({id: 'b', loc: [4, 0]}),
|
||||
@@ -22,8 +9,8 @@ describe('iD.actionReflect', function() {
|
||||
iD.Node({id: 'd', loc: [1, 2]}),
|
||||
iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']})
|
||||
]);
|
||||
var graph2 = iD.actionReflect('-', projection)(graph);
|
||||
expect(graph2).to.deep.equal(graph);
|
||||
graph = iD.actionReflect(['-'], projection)(graph);
|
||||
expect(graph.entity('-').nodes).to.have.length(5);
|
||||
});
|
||||
|
||||
|
||||
@@ -38,9 +25,9 @@ describe('iD.actionReflect', function() {
|
||||
iD.Node({id: 'b', loc: [4, 0]}),
|
||||
iD.Node({id: 'c', loc: [4, 2]}),
|
||||
iD.Node({id: 'd', loc: [1, 2]}),
|
||||
iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a'], tags: { area: 'yes'}})
|
||||
iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']})
|
||||
]);
|
||||
graph = iD.actionReflect('-', projection)(graph);
|
||||
graph = iD.actionReflect(['-'], projection)(graph);
|
||||
expect(graph.entity('a').loc[0]).to.be.closeTo(0, 1e-6);
|
||||
expect(graph.entity('a').loc[1]).to.be.closeTo(2, 1e-6);
|
||||
expect(graph.entity('b').loc[0]).to.be.closeTo(4, 1e-6);
|
||||
@@ -63,9 +50,9 @@ describe('iD.actionReflect', function() {
|
||||
iD.Node({id: 'b', loc: [4, 0]}),
|
||||
iD.Node({id: 'c', loc: [4, 2]}),
|
||||
iD.Node({id: 'd', loc: [1, 2]}),
|
||||
iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a'], tags: { area: 'yes'}})
|
||||
iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']})
|
||||
]);
|
||||
graph = iD.actionReflect('-', projection).useLongAxis(false)(graph);
|
||||
graph = iD.actionReflect(['-'], projection).useLongAxis(false)(graph);
|
||||
expect(graph.entity('a').loc[0]).to.be.closeTo(4, 1e-6);
|
||||
expect(graph.entity('a').loc[1]).to.be.closeTo(0, 1e-6);
|
||||
expect(graph.entity('b').loc[0]).to.be.closeTo(0, 1e-6);
|
||||
|
||||
@@ -185,6 +185,54 @@ describe('iD.geo', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('geoEdgeEqual', function() {
|
||||
it('returns false for inequal edges', function() {
|
||||
expect(iD.geoEdgeEqual(['a','b'], ['a','c'])).to.be.false;
|
||||
});
|
||||
|
||||
it('returns true for equal edges along same direction', function() {
|
||||
expect(iD.geoEdgeEqual(['a','b'], ['a','b'])).to.be.true;
|
||||
});
|
||||
|
||||
it('returns true for equal edges along opposite direction', function() {
|
||||
expect(iD.geoEdgeEqual(['a','b'], ['b','a'])).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('geoAngle', function() {
|
||||
it('returns angle between a and b', function() {
|
||||
var projection = function (_) { return _; };
|
||||
expect(iD.geoAngle({loc:[0, 0]}, {loc:[1, 0]}, projection)).to.be.closeTo(0, 1e-6);
|
||||
expect(iD.geoAngle({loc:[0, 0]}, {loc:[0, 1]}, projection)).to.be.closeTo(Math.PI / 2, 1e-6);
|
||||
expect(iD.geoAngle({loc:[0, 0]}, {loc:[-1, 0]}, projection)).to.be.closeTo(Math.PI, 1e-6);
|
||||
expect(iD.geoAngle({loc:[0, 0]}, {loc:[0, -1]}, projection)).to.be.closeTo(-Math.PI / 2, 1e-6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('geoRotate', function() {
|
||||
it('rotates points around [0, 0]', function() {
|
||||
var points = [[5, 0], [5, 1]],
|
||||
angle = Math.PI,
|
||||
around = [0, 0],
|
||||
result = iD.geoRotate(points, angle, around);
|
||||
expect(result[0][0]).to.be.closeTo(-5, 1e-6);
|
||||
expect(result[0][1]).to.be.closeTo(0, 1e-6);
|
||||
expect(result[1][0]).to.be.closeTo(-5, 1e-6);
|
||||
expect(result[1][1]).to.be.closeTo(-1, 1e-6);
|
||||
});
|
||||
|
||||
it('rotates points around [3, 0]', function() {
|
||||
var points = [[5, 0], [5, 1]],
|
||||
angle = Math.PI,
|
||||
around = [3, 0],
|
||||
result = iD.geoRotate(points, angle, around);
|
||||
expect(result[0][0]).to.be.closeTo(1, 1e-6);
|
||||
expect(result[0][1]).to.be.closeTo(0, 1e-6);
|
||||
expect(result[1][0]).to.be.closeTo(1, 1e-6);
|
||||
expect(result[1][1]).to.be.closeTo(-1, 1e-6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('geoChooseEdge', function() {
|
||||
var projection = function (l) { return l; };
|
||||
projection.invert = projection;
|
||||
|
||||
@@ -1,4 +1,58 @@
|
||||
describe('iD.util', function() {
|
||||
|
||||
describe('utilGetAllNodes', function() {
|
||||
it('gets all descendant nodes of a way', function() {
|
||||
var a = iD.Node({ id: 'a' }),
|
||||
b = iD.Node({ id: 'b' }),
|
||||
w = iD.Way({ id: 'w', nodes: ['a','b','a'] }),
|
||||
graph = iD.Graph([a, b, w]),
|
||||
result = iD.utilGetAllNodes(['w'], graph);
|
||||
|
||||
expect(result).to.have.members([a, b]);
|
||||
expect(result).to.have.lengthOf(2);
|
||||
});
|
||||
|
||||
it('gets all descendant nodes of a relation', function() {
|
||||
var a = iD.Node({ id: 'a' }),
|
||||
b = iD.Node({ id: 'b' }),
|
||||
c = iD.Node({ id: 'c' }),
|
||||
w = iD.Way({ id: 'w', nodes: ['a','b','a'] }),
|
||||
r = iD.Relation({ id: 'r', members: [{id: 'w'}, {id: 'c'}] }),
|
||||
graph = iD.Graph([a, b, c, w, r]),
|
||||
result = iD.utilGetAllNodes(['r'], graph);
|
||||
|
||||
expect(result).to.have.members([a, b, c]);
|
||||
expect(result).to.have.lengthOf(3);
|
||||
});
|
||||
|
||||
it('gets all descendant nodes of multiple ids', function() {
|
||||
var a = iD.Node({ id: 'a' }),
|
||||
b = iD.Node({ id: 'b' }),
|
||||
c = iD.Node({ id: 'c' }),
|
||||
d = iD.Node({ id: 'd' }),
|
||||
e = iD.Node({ id: 'e' }),
|
||||
w1 = iD.Way({ id: 'w1', nodes: ['a','b','a'] }),
|
||||
w2 = iD.Way({ id: 'w2', nodes: ['c','b','a','c'] }),
|
||||
r = iD.Relation({ id: 'r', members: [{id: 'w1'}, {id: 'd'}] }),
|
||||
graph = iD.Graph([a, b, c, d, e, w1, w2, r]),
|
||||
result = iD.utilGetAllNodes(['r', 'w2', 'e'], graph);
|
||||
|
||||
expect(result).to.have.members([a, b, c, d, e]);
|
||||
expect(result).to.have.lengthOf(5);
|
||||
});
|
||||
|
||||
it('handles recursive relations', function() {
|
||||
var a = iD.Node({ id: 'a' }),
|
||||
r1 = iD.Relation({ id: 'r1', members: [{id: 'r2'}] }),
|
||||
r2 = iD.Relation({ id: 'r2', members: [{id: 'r1'}, {id: 'a'}] }),
|
||||
graph = iD.Graph([a, r1, r2]),
|
||||
result = iD.utilGetAllNodes(['r1'], graph);
|
||||
|
||||
expect(result).to.have.members([a]);
|
||||
expect(result).to.have.lengthOf(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('utilTagText', function() {
|
||||
expect(iD.utilTagText({})).to.eql('');
|
||||
expect(iD.utilTagText({tags:{foo:'bar'}})).to.eql('foo=bar');
|
||||
|
||||
Reference in New Issue
Block a user