Merge branch 'master' into add-feature-search-bar

This commit is contained in:
Quincy Morgan
2019-03-02 10:55:32 -05:00
18 changed files with 1277 additions and 487 deletions
+5 -1
View File
@@ -85,12 +85,16 @@ en:
orthogonalize:
title: Square
description:
vertex: Square this corner.
line: Square the corners of this line.
area: Square the corners of this area.
key: S
key: Q
annotation:
vertex: Squared a single corner.
line: Squared the corners of a line.
area: Squared the corners of an area.
end_vertex: This can't be squared because it is an end node.
square_enough: This can't be made more square than it already is.
not_squarish: This can't be made square because it is not squarish.
too_large: This can't be made square because not enough of it is currently visible.
connected_to_hidden: This can't be made square because it is connected to a hidden feature.
+2 -2
View File
@@ -4020,7 +4020,7 @@ en:
highway/footway:
# highway=footway
name: Foot Path
# 'terms: hike,hiking,trackway,trail,walk'
# 'terms: hike,hiking,promenade,trackway,trail,walk'
terms: '<translate with synonyms or related terms for ''Foot Path'', separated by commas>'
highway/footway/conveying:
# 'highway=footway, conveying=*'
@@ -4187,7 +4187,7 @@ en:
highway/steps:
# highway=steps
name: Steps
# 'terms: stairs,staircase'
# 'terms: stairs,staircase,stairway'
terms: '<translate with synonyms or related terms for ''Steps'', separated by commas>'
highway/steps/conveying:
# 'highway=steps, conveying=*'
+2 -2
View File
@@ -426,7 +426,7 @@
"highway/crossing/unmarked": {"fields": ["crossing", "kerb", "tactile_paving"], "geometry": ["vertex"], "addTags": {"highway": "crossing", "crossing": "unmarked"}, "removeTags": {"highway": "crossing", "crossing": "unmarked"}, "tags": {"highway": "crossing"}, "reference": {"key": "highway", "value": "crossing"}, "terms": [], "name": "Unmarked Crossing"},
"highway/cycleway": {"icon": "maki-bicycle", "fields": ["name", "oneway", "surface", "width", "structure", "access", "incline"], "moreFields": ["wheelchair", "lit", "smoothness", "maxspeed", "covered", "dog"], "geometry": ["line"], "tags": {"highway": "cycleway"}, "terms": ["bike"], "name": "Cycle Path"},
"highway/elevator": {"icon": "temaki-elevator", "fields": ["access_simple", "opening_hours", "maxweight", "ref", "wheelchair"], "moreFields": ["maxheight"], "geometry": ["vertex"], "tags": {"highway": "elevator"}, "terms": ["lift"], "name": "Elevator"},
"highway/footway": {"icon": "temaki-pedestrian", "fields": ["name", "surface", "width", "structure", "access", "incline"], "moreFields": ["wheelchair", "lit", "smoothness", "covered", "dog"], "geometry": ["line"], "terms": ["hike", "hiking", "trackway", "trail", "walk"], "tags": {"highway": "footway"}, "name": "Foot Path"},
"highway/footway": {"icon": "temaki-pedestrian", "fields": ["name", "surface", "width", "structure", "access", "incline"], "moreFields": ["wheelchair", "lit", "smoothness", "covered", "dog"], "geometry": ["line"], "terms": ["hike", "hiking", "promenade", "trackway", "trail", "walk"], "tags": {"highway": "footway"}, "name": "Foot Path"},
"highway/footway/zebra-raised": {"icon": "temaki-pedestrian", "fields": ["crossing", "access", "surface", "kerb", "tactile_paving"], "geometry": ["line"], "tags": {"highway": "footway", "footway": "crossing", "crossing": "zebra", "traffic_calming": "table"}, "reference": {"key": "traffic_calming", "value": "table"}, "terms": ["zebra crossing", "marked crossing", "crosswalk", "flat top", "hump", "speed", "slow"], "name": "Marked Crosswalk (Raised)", "searchable": false},
"highway/footway/zebra": {"icon": "temaki-pedestrian", "fields": ["crossing", "access", "surface", "kerb", "tactile_paving"], "geometry": ["line"], "tags": {"highway": "footway", "footway": "crossing", "crossing": "zebra"}, "reference": {"key": "footway", "value": "crossing"}, "terms": ["zebra crossing", "marked crossing", "crosswalk"], "name": "Marked Crosswalk", "searchable": false},
"highway/footway/conveying": {"icon": "temaki-pedestrian", "fields": ["name", "conveying", "access_simple", "lit", "width", "wheelchair"], "geometry": ["line"], "terms": ["moving sidewalk", "autwalk", "skywalk", "travolator", "travelator", "travellator", "conveyor"], "tags": {"highway": "footway", "conveying": "*"}, "name": "Moving Walkway"},
@@ -462,7 +462,7 @@
"highway/service/parking_aisle": {"icon": "iD-highway-service", "geometry": ["line"], "tags": {"highway": "service", "service": "parking_aisle"}, "reference": {"key": "service", "value": "parking_aisle"}, "name": "Parking Aisle"},
"highway/services": {"icon": "maki-car", "fields": ["{highway/rest_area}"], "moreFields": ["{highway/rest_area}"], "geometry": ["point", "vertex", "area"], "tags": {"highway": "services"}, "terms": ["services", "travel plaza", "service station"], "name": "Service Area"},
"highway/speed_camera": {"icon": "maki-attraction", "geometry": ["point", "vertex"], "fields": ["direction", "ref", "maxspeed"], "tags": {"highway": "speed_camera"}, "terms": [], "name": "Speed Camera"},
"highway/steps": {"icon": "iD-highway-steps", "fields": ["surface", "lit", "width", "incline_steps", "handrail", "step_count"], "geometry": ["line"], "tags": {"highway": "steps"}, "terms": ["stairs", "staircase"], "name": "Steps"},
"highway/steps": {"icon": "iD-highway-steps", "fields": ["incline_steps", "handrail", "step_count", "surface", "lit", "width"], "moreFields": ["covered", "dog"], "geometry": ["line"], "tags": {"highway": "steps"}, "terms": ["stairs", "staircase", "stairway"], "name": "Steps"},
"highway/steps/conveying": {"icon": "maki-entrance", "fields": ["name", "incline_steps", "conveying", "access_simple", "lit", "width", "handrail", "step_count"], "geometry": ["line"], "terms": ["moving staircase", "moving stairway", "people mover"], "tags": {"highway": "steps", "conveying": "*"}, "name": "Escalator"},
"highway/stop": {"icon": "temaki-stop", "fields": ["stop", "direction_vertex"], "geometry": ["vertex"], "tags": {"highway": "stop"}, "terms": ["stop", "halt", "sign"], "name": "Stop Sign"},
"highway/street_lamp": {"icon": "temaki-bulb", "geometry": ["point", "vertex"], "tags": {"highway": "street_lamp"}, "fields": ["lamp_type", "direction", "ref"], "terms": ["streetlight", "street light", "lamp", "light", "gaslight"], "name": "Street Lamp"},
@@ -21,6 +21,7 @@
"terms": [
"hike",
"hiking",
"promenade",
"trackway",
"trail",
"walk"
+10 -5
View File
@@ -1,12 +1,16 @@
{
"icon": "iD-highway-steps",
"fields": [
"surface",
"lit",
"width",
"incline_steps",
"handrail",
"step_count"
"step_count",
"surface",
"lit",
"width"
],
"moreFields": [
"covered",
"dog"
],
"geometry": [
"line"
@@ -16,7 +20,8 @@
},
"terms": [
"stairs",
"staircase"
"staircase",
"stairway"
],
"name": "Steps"
}
+7 -3
View File
@@ -113,14 +113,18 @@
"orthogonalize": {
"title": "Square",
"description": {
"vertex": "Square this corner.",
"line": "Square the corners of this line.",
"area": "Square the corners of this area."
},
"key": "S",
"key": "Q",
"annotation": {
"vertex": "Squared a single corner.",
"line": "Squared the corners of a line.",
"area": "Squared the corners of an area."
},
"end_vertex": "This can't be squared because it is an end node.",
"square_enough": "This can't be made more square than it already is.",
"not_squarish": "This can't be made square because it is not squarish.",
"too_large": "This can't be made square because not enough of it is currently visible.",
"connected_to_hidden": "This can't be made square because it is connected to a hidden feature."
@@ -5600,7 +5604,7 @@
},
"highway/footway": {
"name": "Foot Path",
"terms": "hike,hiking,trackway,trail,walk"
"terms": "hike,hiking,promenade,trackway,trail,walk"
},
"highway/footway/zebra-raised": {
"name": "Marked Crosswalk (Raised)",
@@ -5744,7 +5748,7 @@
},
"highway/steps": {
"name": "Steps",
"terms": "stairs,staircase"
"terms": "stairs,staircase,stairway"
},
"highway/steps/conveying": {
"name": "Escalator",
+216 -112
View File
@@ -1,34 +1,65 @@
import _clone from 'lodash-es/clone';
import _uniq from 'lodash-es/uniq';
import _cloneDeep from 'lodash-es/cloneDeep';
import { actionDeleteNode } from './delete_node';
import { geoVecInterp, geoVecLength } from '../geo';
import {
geoVecAdd,
geoVecEqual,
geoVecInterp,
geoVecLength,
geoVecNormalize,
geoVecNormalizedDot,
geoVecProject,
geoVecScale,
geoVecSubtract
} from '../geo';
/*
* Based on https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/potlatch2/tools/Quadrilateralise.as
*/
export function actionOrthogonalize(wayId, projection) {
var threshold = 12, // degrees within right or straight to alter
lowerThreshold = Math.cos((90 - threshold) * Math.PI / 180),
upperThreshold = Math.cos(threshold * Math.PI / 180);
export function actionOrthogonalize(wayID, projection, vertexID) {
var epsilon = 1e-4;
var threshold = 13; // degrees within right or straight to alter
// We test normalized dot products so we can compare as cos(angle)
var lowerThreshold = Math.cos((90 - threshold) * Math.PI / 180);
var upperThreshold = Math.cos(threshold * Math.PI / 180);
var action = function(graph, t) {
if (t === null || !isFinite(t)) t = 1;
t = Math.min(Math.max(+t, 0), 1);
var way = graph.entity(wayId),
nodes = graph.childNodes(way),
points = _uniq(nodes).map(function(n) { return projection(n.loc); }),
corner = {i: 0, dotp: 1},
epsilon = 1e-4,
node, loc, score, motions, i, j;
var way = graph.entity(wayID);
way = way.removeNode(''); // sanity check - remove any consecutive duplicates
graph = graph.replace(way);
var isClosed = way.isClosed();
var nodes = _clone(graph.childNodes(way));
if (isClosed) nodes.pop();
if (vertexID !== undefined) {
nodes = nodeSubset(nodes, vertexID, isClosed);
if (nodes.length !== 3) return graph;
}
// note: all geometry functions here use the unclosed node/point/coord list
var nodeCount = {};
var points = [];
var corner = { i: 0, dotp: 1 };
var node, point, loc, score, motions, i, j;
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
nodeCount[node.id] = (nodeCount[node.id] || 0) + 1;
points.push({ id: node.id, coord: projection(node.loc) });
}
if (points.length === 3) { // move only one vertex for right triangle
for (i = 0; i < 1000; i++) {
motions = points.map(calcMotion);
points[corner.i] = addPoints(points[corner.i], motions[corner.i]);
points[corner.i].coord = geoVecAdd(points[corner.i].coord, motions[corner.i]);
score = corner.dotp;
if (score < epsilon) {
break;
@@ -36,22 +67,45 @@ export function actionOrthogonalize(wayId, projection) {
}
node = graph.entity(nodes[corner.i].id);
loc = projection.invert(points[corner.i]);
loc = projection.invert(points[corner.i].coord);
graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t)));
} else {
var best,
originalPoints = _clone(points);
var straights = [];
var simplified = [];
// Remove points from nearly straight sections..
// This produces a simplified shape to orthogonalize
for (i = 0; i < points.length; i++) {
point = points[i];
var dotp = 0;
if (isClosed || (i > 0 && i < points.length - 1)) {
var a = points[(i - 1 + points.length) % points.length];
var b = points[(i + 1) % points.length];
dotp = Math.abs(normalizedDotProduct(a.coord, b.coord, point.coord));
}
if (dotp > upperThreshold) {
straights.push(point);
} else {
simplified.push(point);
}
}
// Orthogonalize the simplified shape
var bestPoints = _cloneDeep(simplified);
var originalPoints = _cloneDeep(simplified);
score = Infinity;
for (i = 0; i < 1000; i++) {
motions = points.map(calcMotion);
motions = simplified.map(calcMotion);
for (j = 0; j < motions.length; j++) {
points[j] = addPoints(points[j],motions[j]);
simplified[j].coord = geoVecAdd(simplified[j].coord, motions[j]);
}
var newScore = squareness(points);
var newScore = calcScore(simplified, isClosed);
if (newScore < score) {
best = _clone(points);
bestPoints = _cloneDeep(simplified);
score = newScore;
}
if (score < epsilon) {
@@ -59,30 +113,41 @@ export function actionOrthogonalize(wayId, projection) {
}
}
points = best;
var bestCoords = bestPoints.map(function(p) { return p.coord; });
if (isClosed) bestCoords.push(bestCoords[0]);
for (i = 0; i < points.length; i++) {
// only move the points that actually moved
if (originalPoints[i][0] !== points[i][0] || originalPoints[i][1] !== points[i][1]) {
loc = projection.invert(points[i]);
node = graph.entity(nodes[i].id);
// move the nodes that should move
for (i = 0; i < bestPoints.length; i++) {
point = bestPoints[i];
if (!geoVecEqual(originalPoints[i].coord, point.coord)) {
node = graph.entity(point.id);
loc = projection.invert(point.coord);
graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t)));
}
}
// remove empty nodes on straight sections
for (i = 0; t === 1 && i < points.length; i++) {
node = graph.entity(nodes[i].id);
// move the nodes along straight segments
for (i = 0; i < straights.length; i++) {
point = straights[i];
if (nodeCount[point.id] > 1) continue; // skip self-intersections
if (graph.parentWays(node).length > 1 ||
graph.parentRelations(node).length ||
node.hasInterestingTags()) {
continue;
}
node = graph.entity(point.id);
var dotp = normalizedDotProduct(i, points);
if (dotp < -1 + epsilon) {
if (t === 1 &&
graph.parentWays(node).length === 1 &&
graph.parentRelations(node).length === 0 &&
!node.hasInterestingTags()
) {
// remove uninteresting points..
graph = actionDeleteNode(node.id)(graph);
} else {
// move interesting points to the nearest edge..
var choice = geoVecProject(point.coord, bestCoords);
if (choice) {
loc = projection.invert(choice.target);
graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t)));
}
}
}
}
@@ -90,107 +155,146 @@ export function actionOrthogonalize(wayId, projection) {
return graph;
function calcMotion(b, i, array) {
var a = array[(i - 1 + array.length) % array.length],
c = array[(i + 1) % array.length],
p = subtractPoints(a, b),
q = subtractPoints(c, b),
scale, dotp;
function calcMotion(point, i, array) {
// don't try to move the endpoints of a non-closed way.
if (!isClosed && (i === 0 || i === array.length - 1)) return [0, 0];
// don't try to move a node that appears more than once (self intersection)
if (nodeCount[array[i].id] > 1) return [0, 0];
scale = 2 * Math.min(geoVecLength(p, [0, 0]), geoVecLength(q, [0, 0]));
p = normalizePoint(p, 1.0);
q = normalizePoint(q, 1.0);
var a = array[(i - 1 + array.length) % array.length].coord;
var origin = point.coord;
var b = array[(i + 1) % array.length].coord;
var p = geoVecSubtract(a, origin);
var q = geoVecSubtract(b, origin);
dotp = filterDotProduct(p[0] * q[0] + p[1] * q[1]);
var scale = 2 * Math.min(geoVecLength(p), geoVecLength(q));
p = geoVecNormalize(p);
q = geoVecNormalize(q);
// nasty hack to deal with almost-straight segments (angle is closer to 180 than to 90/270).
if (array.length > 3) {
if (dotp < -0.707106781186547) {
dotp += 1.0;
}
} else if (dotp && Math.abs(dotp) < corner.dotp) {
var dotp = (p[0] * q[0] + p[1] * q[1]);
var val = Math.abs(dotp);
if (val < lowerThreshold) { // nearly orthogonal
corner.i = i;
corner.dotp = Math.abs(dotp);
corner.dotp = val;
var vec = geoVecNormalize(geoVecAdd(p, q));
return geoVecScale(vec, 0.1 * dotp * scale);
}
return normalizePoint(addPoints(p, q), 0.1 * dotp * scale);
return [0, 0]; // do nothing
}
};
function squareness(points) {
return points.reduce(function(sum, val, i, array) {
var dotp = normalizedDotProduct(i, array);
dotp = filterDotProduct(dotp);
return sum + 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1)));
}, 0);
}
function normalizedDotProduct(i, points) {
var a = points[(i - 1 + points.length) % points.length],
b = points[i],
c = points[(i + 1) % points.length],
p = subtractPoints(a, b),
q = subtractPoints(c, b);
p = normalizePoint(p, 1.0);
q = normalizePoint(q, 1.0);
return p[0] * q[0] + p[1] * q[1];
}
function subtractPoints(a, b) {
return [a[0] - b[0], a[1] - b[1]];
}
function addPoints(a, b) {
return [a[0] + b[0], a[1] + b[1]];
}
function normalizePoint(point, scale) {
var vector = [0, 0];
var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]);
if (length !== 0) {
vector[0] = point[0] / length;
vector[1] = point[1] / length;
function normalizedDotProduct(a, b, origin) {
if (geoVecEqual(origin, a) || geoVecEqual(origin, b)) {
return 1; // coincident points, treat as straight and try to remove
}
vector[0] *= scale;
vector[1] *= scale;
return vector;
return geoVecNormalizedDot(a, b, origin);
}
function filterDotProduct(dotp) {
if (lowerThreshold > Math.abs(dotp) || Math.abs(dotp) > upperThreshold) {
return dotp;
var val = Math.abs(dotp);
if (val < epsilon) {
return 0; // already orthogonal
} else if (val < lowerThreshold || val > upperThreshold) {
return dotp; // can be adjusted
} else {
return null; // ignore vertex
}
}
function calcScore(points, isClosed) {
var score = 0;
var first = isClosed ? 0 : 1;
var last = isClosed ? points.length : points.length - 1;
var coords = points.map(function(p) { return p.coord; });
for (var i = first; i < last; i++) {
var a = coords[(i - 1 + coords.length) % coords.length];
var origin = coords[i];
var b = coords[(i + 1) % coords.length];
var dotp = filterDotProduct(normalizedDotProduct(a, b, origin));
if (dotp === null) continue; // ignore vertex
score = score + 2.0 * Math.min(Math.abs(dotp - 1.0), Math.min(Math.abs(dotp), Math.abs(dotp + 1)));
}
return 0;
return score;
}
// similar to calcScore, but returns quickly if there is something to do
function canOrthogonalize(coords, isClosed) {
var score = null;
var first = isClosed ? 0 : 1;
var last = isClosed ? coords.length : coords.length - 1;
for (var i = first; i < last; i++) {
var a = coords[(i - 1 + coords.length) % coords.length];
var origin = coords[i];
var b = coords[(i + 1) % coords.length];
var dotp = filterDotProduct(normalizedDotProduct(a, b, origin));
if (dotp === null) continue; // ignore vertex
if (Math.abs(dotp) > 0) return 1; // something to do
score = 0; // already square
}
return score;
}
// if we are only orthogonalizing one vertex,
// get that vertex and the previous and next
function nodeSubset(nodes, vertexID, isClosed) {
var first = isClosed ? 0 : 1;
var last = isClosed ? nodes.length : nodes.length - 1;
for (var i = first; i < last; i++) {
if (nodes[i].id === vertexID) {
return [
nodes[(i - 1 + nodes.length) % nodes.length],
nodes[i],
nodes[(i + 1) % nodes.length]
];
}
}
return [];
}
action.disabled = function(graph) {
var way = graph.entity(wayId),
nodes = graph.childNodes(way),
points = _uniq(nodes).map(function(n) { return projection(n.loc); });
var way = graph.entity(wayID);
way = way.removeNode(''); // sanity check - remove any consecutive duplicates
graph = graph.replace(way);
if (squareness(points)) {
return false;
var isClosed = way.isClosed();
var nodes = _clone(graph.childNodes(way));
if (isClosed) nodes.pop();
if (vertexID !== undefined) {
nodes = nodeSubset(nodes, vertexID, isClosed);
if (nodes.length !== 3) return 'end_vertex';
}
return 'not_squarish';
var coords = nodes.map(function(n) { return projection(n.loc); });
var score = canOrthogonalize(coords, isClosed);
if (score === null) {
return 'not_squarish';
} else if (score === 0) {
return 'square_enough';
} else {
return false;
}
};
action.transitionable = true;
return action;
}
+1 -1
View File
@@ -5,7 +5,7 @@ import _values from 'lodash-es/values';
/*
iD.Difference represents the difference between two graphs.
iD.coreDifference represents the difference between two graphs.
It knows how to calculate the set of entities that were
created, modified, or deleted, and also contains the logic
for recursively extending a difference to the complete set
+3
View File
@@ -36,5 +36,8 @@ export { geoVecEqual } from './vector.js';
export { geoVecFloor } from './vector.js';
export { geoVecInterp } from './vector.js';
export { geoVecLength } from './vector.js';
export { geoVecNormalize } from './vector.js';
export { geoVecNormalizedDot } from './vector.js';
export { geoVecProject } from './vector.js';
export { geoVecSubtract } from './vector.js';
export { geoVecScale } from './vector.js';
+61 -4
View File
@@ -37,11 +37,21 @@ export function geoVecInterp(a, b, t) {
// http://jsperf.com/id-dist-optimization
export function geoVecLength(a, b) {
b = b || [0, 0];
var x = a[0] - b[0];
var y = a[1] - b[1];
return Math.sqrt((x * x) + (y * y));
}
// get a unit vector
export function geoVecNormalize(a) {
var length = Math.sqrt((a[0] * a[0]) + (a[1] * a[1]));
if (length !== 0) {
return geoVecScale(a, 1 / length);
}
return [0, 0];
}
// Return the counterclockwise angle in the range (-pi, pi)
// between the positive X axis and the line intersecting a and b.
export function geoVecAngle(a, b) {
@@ -51,8 +61,17 @@ export function geoVecAngle(a, b) {
// dot product
export function geoVecDot(a, b, origin) {
origin = origin || [0, 0];
return (a[0] - origin[0]) * (b[0] - origin[0]) +
(a[1] - origin[1]) * (b[1] - origin[1]);
var p = geoVecSubtract(a, origin);
var q = geoVecSubtract(b, origin);
return (p[0]) * (q[0]) + (p[1]) * (q[1]);
}
// normalized dot product
export function geoVecNormalizedDot(a, b, origin) {
origin = origin || [0, 0];
var p = geoVecNormalize(geoVecSubtract(a, origin));
var q = geoVecNormalize(geoVecSubtract(b, origin));
return geoVecDot(p, q);
}
// 2D cross product of OA and OB vectors, returns magnitude of Z vector
@@ -60,7 +79,45 @@ export function geoVecDot(a, b, origin) {
// negative for clockwise turn, and zero if the points are collinear.
export function geoVecCross(a, b, origin) {
origin = origin || [0, 0];
return (a[0] - origin[0]) * (b[1] - origin[1]) -
(a[1] - origin[1]) * (b[0] - origin[0]);
var p = geoVecSubtract(a, origin);
var q = geoVecSubtract(b, origin);
return (p[0]) * (q[1]) - (p[1]) * (q[0]);
}
// find closest orthogonal projection of point onto points array
export function geoVecProject(a, points) {
var min = Infinity;
var idx;
var target;
for (var i = 0; i < points.length - 1; i++) {
var o = points[i];
var s = geoVecSubtract(points[i + 1], o);
var v = geoVecSubtract(a, o);
var proj = geoVecDot(v, s) / geoVecDot(s, s);
var 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 dist = geoVecLength(p, a);
if (dist < min) {
min = dist;
idx = i + 1;
target = p;
}
}
if (idx !== undefined) {
return { index: idx, distance: min, target: target };
} else {
return null;
}
}
+41 -13
View File
@@ -6,31 +6,59 @@ import { behaviorOperation } from '../behavior/index';
export function operationOrthogonalize(selectedIDs, context) {
var entityId = selectedIDs[0],
entity = context.entity(entityId),
extent = entity.extent(context.graph()),
geometry = context.geometry(entityId),
action = actionOrthogonalize(entityId, context.projection);
var _entityID;
var _entity;
var _geometry;
var action = chooseAction();
function chooseAction() {
if (selectedIDs.length !== 1) return null;
_entityID = selectedIDs[0];
_entity = context.entity(_entityID);
_geometry = context.geometry(_entityID);
// square a line/area
if (_entity.type === 'way' && _uniq(_entity.nodes).length > 2 ) {
return actionOrthogonalize(_entityID, context.projection);
// square a single vertex
} else if (_geometry === 'vertex') {
var graph = context.graph();
var parents = graph.parentWays(_entity);
if (parents.length === 1) {
var way = parents[0];
if (way.nodes.indexOf(_entityID) !== -1) {
return actionOrthogonalize(way.id, context.projection, _entityID);
}
}
}
return null;
}
var operation = function() {
if (!action) return;
context.perform(action, operation.annotation());
};
operation.available = function() {
return selectedIDs.length === 1 &&
entity.type === 'way' &&
entity.isClosed() &&
_uniq(entity.nodes).length > 2;
return Boolean(action);
};
operation.disabled = function() {
if (!action) return '';
var extent = _entity.extent(context.graph());
var reason;
if (extent.percentContainedIn(context.extent()) < 0.8) {
if (_geometry !== 'vertex' && extent.percentContainedIn(context.extent()) < 0.8) {
reason = 'too_large';
} else if (context.hasHiddenConnections(entityId)) {
} else if (context.hasHiddenConnections(_entityID)) {
reason = 'connected_to_hidden';
}
return action.disabled(context.graph()) || reason;
@@ -41,12 +69,12 @@ export function operationOrthogonalize(selectedIDs, context) {
var disable = operation.disabled();
return disable ?
t('operations.orthogonalize.' + disable) :
t('operations.orthogonalize.description.' + geometry);
t('operations.orthogonalize.description.' + _geometry);
};
operation.annotation = function() {
return t('operations.orthogonalize.annotation.' + geometry);
return t('operations.orthogonalize.annotation.' + _geometry);
};
-2
View File
@@ -195,7 +195,6 @@ _extend(osmWay.prototype, {
// returns an object with the tag that implies this is an area, if any
tagSuggestingArea: function() {
if (this.tags.area === 'yes') return { area: 'yes' };
if (this.tags.area === 'no') return null;
@@ -230,7 +229,6 @@ _extend(osmWay.prototype, {
},
isArea: function() {
if (this.tags.area === 'yes')
return true;
if (!this.isClosed() || this.tags.area === 'no')
+7 -32
View File
@@ -1,7 +1,7 @@
import { select as d3_select } from 'd3-selection';
import { t } from '../../util/locale';
import { geoSphericalDistance } from '../../geo';
import { geoSphericalDistance, geoVecNormalizedDot } from '../../geo';
export function pointBox(loc, context) {
@@ -117,45 +117,20 @@ export function isMostlySquare(points) {
var threshold = 15; // degrees within right or straight
var lowerBound = Math.cos((90 - threshold) * Math.PI / 180); // near right
var upperBound = Math.cos(threshold * Math.PI / 180); // near straight
var mag;
for (var i = 0; i < points.length; i++) {
mag = Math.abs(normalizedDotProduct(i, points));
var a = points[(i - 1 + points.length) % points.length];
var origin = points[i];
var b = points[(i + 1) % points.length];
var dotp = geoVecNormalizedDot(a, b, origin);
var mag = Math.abs(dotp);
if (mag > lowerBound && mag < upperBound) {
return false;
}
}
return true;
function normalizedDotProduct(i, points) {
var a = points[(i - 1 + points.length) % points.length];
var b = points[i];
var c = points[(i + 1) % points.length];
var p = subtractPoints(a, b);
var q = subtractPoints(c, b);
p = normalizePoint(p);
q = normalizePoint(q);
return p[0] * q[0] + p[1] * q[1];
function subtractPoints(a, b) {
return [a[0] - b[0], a[1] - b[1]];
}
function normalizePoint(point) {
var vector = [0, 0];
var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]);
if (length !== 0) {
vector[0] = point[0] / length;
vector[1] = point[1] / length;
}
return vector;
}
}
}
+1 -1
View File
@@ -68,7 +68,7 @@
"js-yaml": "^3.9.0",
"json-stringify-pretty-compact": "^1.1.0",
"jsonschema": "^1.1.0",
"mapillary-js": "2.16.0",
"mapillary-js": "2.17.0",
"mapillary_sprite_source": "^1.7.0",
"minimist": "^1.2.0",
"mocha": "^6.0.0",
+35 -37
View File
@@ -1,24 +1,24 @@
describe('iD.actionCopyEntities', function () {
it('copies a node', function () {
var a = iD.osmNode({id: 'a'}),
base = iD.coreGraph([a]),
head = iD.actionCopyEntities(['a'], base)(base),
diff = iD.Difference(base, head),
created = diff.created();
var a = iD.osmNode({id: 'a'});
var base = iD.coreGraph([a]);
var head = iD.actionCopyEntities(['a'], base)(base);
var diff = iD.coreDifference(base, head);
var created = diff.created();
expect(head.hasEntity('a')).to.be.ok;
expect(created).to.have.length(1);
});
it('copies a way', function () {
var a = iD.osmNode({id: 'a'}),
b = iD.osmNode({id: 'b'}),
w = iD.osmWay({id: 'w', nodes: ['a', 'b']}),
base = iD.coreGraph([a, b, w]),
action = iD.actionCopyEntities(['w'], base),
head = action(base),
diff = iD.Difference(base, head),
created = diff.created();
var a = iD.osmNode({id: 'a'});
var b = iD.osmNode({id: 'b'});
var w = iD.osmWay({id: 'w', nodes: ['a', 'b']});
var base = iD.coreGraph([a, b, w]);
var action = iD.actionCopyEntities(['w'], base);
var head = action(base);
var diff = iD.coreDifference(base, head);
var created = diff.created();
expect(head.hasEntity('w')).to.be.ok;
expect(created).to.have.length(3);
@@ -26,13 +26,13 @@ describe('iD.actionCopyEntities', function () {
it('copies multiple nodes', function () {
var base = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'})
]),
action = iD.actionCopyEntities(['a', 'b'], base),
head = action(base),
diff = iD.Difference(base, head),
created = diff.created();
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'})
]);
var action = iD.actionCopyEntities(['a', 'b'], base);
var head = action(base);
var diff = iD.coreDifference(base, head);
var created = diff.created();
expect(head.hasEntity('a')).to.be.ok;
expect(head.hasEntity('b')).to.be.ok;
@@ -41,29 +41,27 @@ describe('iD.actionCopyEntities', function () {
it('copies multiple ways, keeping the same connections', function () {
var base = iD.coreGraph([
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmWay({id: 'w1', nodes: ['a', 'b']}),
iD.osmWay({id: 'w2', nodes: ['b', 'c']})
]),
action = iD.actionCopyEntities(['w1', 'w2'], base),
head = action(base),
diff = iD.Difference(base, head),
created = diff.created();
iD.osmNode({id: 'a'}),
iD.osmNode({id: 'b'}),
iD.osmNode({id: 'c'}),
iD.osmWay({id: 'w1', nodes: ['a', 'b']}),
iD.osmWay({id: 'w2', nodes: ['b', 'c']})
]);
var action = iD.actionCopyEntities(['w1', 'w2'], base);
var head = action(base);
var diff = iD.coreDifference(base, head);
var created = diff.created();
expect(created).to.have.length(5);
expect(action.copies().w1.nodes[1]).to.eql(action.copies().w2.nodes[0]);
});
it('obtains source entities from an alternate graph', function () {
var a = iD.osmNode({id: 'a'}),
old = iD.coreGraph([a]),
base = iD.coreGraph(),
action = iD.actionCopyEntities(['a'], old),
head = action(base),
diff = iD.Difference(base, head);
diff.created();
var a = iD.osmNode({id: 'a'});
var old = iD.coreGraph([a]);
var base = iD.coreGraph();
var action = iD.actionCopyEntities(['a'], old);
var head = action(base);
expect(head.hasEntity('a')).not.to.be.ok;
expect(Object.keys(action.copies())).to.have.length(1);
+634 -95
View File
@@ -1,8 +1,13 @@
describe('iD.actionOrthogonalize', function () {
var projection = d3.geoMercator();
var projection = function (l) { return l; };
projection.invert = projection;
it('orthogonalizes a perfect quad', function () {
var graph = iD.coreGraph([
describe('closed paths', function () {
it('orthogonalizes a perfect quad', function () {
// d --- c
// | |
// a --- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
@@ -10,37 +15,47 @@ describe('iD.actionOrthogonalize', function () {
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(5);
});
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(5);
});
it('orthogonalizes a quad', function () {
var graph = iD.coreGraph([
it('orthogonalizes a quad', function () {
// d --- c
// | |
// a --- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [4, 0]}),
iD.osmNode({id: 'c', loc: [3, 2]}),
iD.osmNode({id: 'b', loc: [2.1, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(5);
});
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(5);
});
it('orthogonalizes a triangle', function () {
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [3, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
it('orthogonalizes a triangle', function () {
// a
// | \
// | \
// b - c
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 3]}),
iD.osmNode({id: 'b', loc: [0.1, 0]}),
iD.osmNode({id: 'c', loc: [3, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'a']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(4);
});
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(4);
});
it('deletes empty redundant nodes', function() {
var graph = iD.coreGraph([
it('deletes empty redundant nodes', function() {
// e - d - c
// | |
// a ----- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
@@ -49,12 +64,15 @@ describe('iD.actionOrthogonalize', function () {
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'a']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.hasEntity('d')).to.eq(undefined);
});
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.hasEntity('d')).to.eq(undefined);
});
it('preserves non empty redundant nodes', function() {
var graph = iD.coreGraph([
it('preserves non empty redundant nodes', function() {
// e - d - c
// | |
// a ----- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
@@ -63,76 +81,597 @@ describe('iD.actionOrthogonalize', function () {
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'a']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(6);
expect(graph.hasEntity('d')).to.not.eq(undefined);
});
it('preserves the shape of skinny quads', function () {
var tests = [
[
[-77.0339864831478, 38.8616391227204],
[-77.0209775298677, 38.8613609264884],
[-77.0210405781065, 38.8607390721519],
[-77.0339024188294, 38.8610663645859]
],
[
[-89.4706683, 40.6261177],
[-89.4706664, 40.6260574],
[-89.4693973, 40.6260830],
[-89.4694012, 40.6261355]
]
];
for (var i = 0; i < tests.length; i++) {
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: tests[i][0]}),
iD.osmNode({id: 'b', loc: tests[i][1]}),
iD.osmNode({id: 'c', loc: tests[i][2]}),
iD.osmNode({id: 'd', loc: tests[i][3]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']})
]),
initialWidth = iD.geoSphericalDistance(graph.entity('a').loc, graph.entity('b').loc),
finalWidth;
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(6);
expect(graph.hasEntity('d')).to.not.eq(undefined);
});
finalWidth = iD.geoSphericalDistance(graph.entity('a').loc, graph.entity('b').loc);
expect(finalWidth / initialWidth).within(0.90, 1.10);
}
});
it('only moves nodes which are near right or near straight', function() {
var graph = iD.coreGraph([
it('only moves nodes which are near right or near straight', function() {
// f - e
// | \
// | d - c
// | |
// a -------- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [3, 0.001]}),
iD.osmNode({id: 'b', loc: [3.1, 0]}),
iD.osmNode({id: 'c', loc: [3, 1]}),
iD.osmNode({id: 'd', loc: [2, 1]}),
iD.osmNode({id: 'e', loc: [1, 2]}),
iD.osmNode({id: 'f', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']})
]),
diff = iD.Difference(graph, iD.actionOrthogonalize('-', projection)(graph));
]);
expect(Object.keys(diff.changes()).sort()).to.eql(['a', 'b', 'c', 'f']);
var diff = iD.coreDifference(graph, iD.actionOrthogonalize('-', projection)(graph));
expect(Object.keys(diff.changes()).sort()).to.eql(['a', 'b', 'c', 'f']);
});
it('does not move or remove self-intersecting nodes', function() {
// f -- g
// | |
// e --- d - c
// | |
// a -- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [ 0, -1]}),
iD.osmNode({id: 'b', loc: [ 1, -1]}),
iD.osmNode({id: 'c', loc: [ 0, 1]}),
iD.osmNode({id: 'd', loc: [ 0.1, 0]}),
iD.osmNode({id: 'e', loc: [-1, 0]}),
iD.osmNode({id: 'f', loc: [-1, 1]}),
iD.osmNode({id: 'g', loc: [ 0, 1]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'd', 'a']})
]);
var diff = iD.coreDifference(graph, iD.actionOrthogonalize('-', projection)(graph));
expect(diff.changes().d).to.be.undefined;
expect(graph.hasEntity('d')).to.be.ok;
});
it('preserves the shape of skinny quads', function () {
var projection = iD.d3.geoMercator();
var tests = [[
[-77.0339864831478, 38.8616391227204],
[-77.0209775298677, 38.8613609264884],
[-77.0210405781065, 38.8607390721519],
[-77.0339024188294, 38.8610663645859]
], [
[-89.4706683, 40.6261177],
[-89.4706664, 40.6260574],
[-89.4693973, 40.6260830],
[-89.4694012, 40.6261355]
]];
for (var i = 0; i < tests.length; i++) {
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: tests[i][0]}),
iD.osmNode({id: 'b', loc: tests[i][1]}),
iD.osmNode({id: 'c', loc: tests[i][2]}),
iD.osmNode({id: 'd', loc: tests[i][3]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']})
]);
var initialWidth = iD.geoSphericalDistance(graph.entity('a').loc, graph.entity('b').loc);
graph = iD.actionOrthogonalize('-', projection)(graph);
var finalWidth = iD.geoSphericalDistance(graph.entity('a').loc, graph.entity('b').loc);
expect(finalWidth / initialWidth).within(0.90, 1.10);
}
});
});
describe('open paths', function () {
it('orthogonalizes a perfect quad path', function () {
// d --- c
// |
// a --- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(4);
});
it('orthogonalizes a quad path', function () {
// d --- c
// |
// a --- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2.1, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(4);
});
it('orthogonalizes a 3-point path', function () {
// a
// |
// |
// b - c
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 3]}),
iD.osmNode({id: 'b', loc: [0.1, 0]}),
iD.osmNode({id: 'c', loc: [3, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(3);
});
it('deletes empty redundant nodes', function() {
// e - d - c
// |
// a ----- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [1, 2]}),
iD.osmNode({id: 'e', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.hasEntity('d')).to.be.undefined;
});
it('preserves non empty redundant nodes', function() {
// e - d - c
// |
// a ----- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [1, 2], tags: {foo: 'bar'}}),
iD.osmNode({id: 'e', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph);
expect(graph.entity('-').nodes).to.have.length(5);
expect(graph.hasEntity('d')).to.be.ok;
});
it('only moves non-endpoint nodes which are near right or near straight', function() {
// f - e
// \
// d - c
// |
// a -------- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [3.1, 0]}),
iD.osmNode({id: 'c', loc: [3, 1]}),
iD.osmNode({id: 'd', loc: [2, 1]}),
iD.osmNode({id: 'e', loc: [1, 2]}),
iD.osmNode({id: 'f', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f']})
]);
var diff = iD.coreDifference(graph, iD.actionOrthogonalize('-', projection)(graph));
expect(Object.keys(diff.changes()).sort()).to.eql(['b', 'c']);
});
it('does not move or remove self-intersecting nodes', function() {
// f -- g
// | |
// e --- d - c
var graph = iD.coreGraph([
iD.osmNode({id: 'c', loc: [ 0, 1]}),
iD.osmNode({id: 'd', loc: [ 0.1, 0]}),
iD.osmNode({id: 'e', loc: [-1, 0]}),
iD.osmNode({id: 'f', loc: [-1, 1]}),
iD.osmNode({id: 'g', loc: [ 0, 1]}),
iD.osmWay({id: '-', nodes: ['c', 'd', 'e', 'f', 'g', 'd']})
]);
var diff = iD.coreDifference(graph, iD.actionOrthogonalize('-', projection)(graph));
expect(diff.changes().d).to.be.undefined;
expect(graph.hasEntity('d')).to.be.ok;
});
});
describe('vertices', function () {
it('orthogonalizes a single vertex in a quad', function () {
// d --- c
// | |
// a --- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2.1, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']})
]);
var diff = iD.coreDifference(graph, iD.actionOrthogonalize('-', projection, 'b')(graph));
expect(diff.changes().a).to.be.undefined;
expect(diff.changes().b).to.be.not.undefined;
expect(diff.changes().c).to.be.undefined;
expect(diff.changes().d).to.be.undefined;
});
it('orthogonalizes a single vertex in a triangle', function () {
// a
// | \
// | \
// b - c
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 3]}),
iD.osmNode({id: 'b', loc: [0.1, 0]}),
iD.osmNode({id: 'c', loc: [3, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'a']})
]);
var diff = iD.coreDifference(graph, iD.actionOrthogonalize('-', projection, 'b')(graph));
expect(diff.changes().a).to.be.undefined;
expect(diff.changes().b).to.be.not.undefined;
expect(diff.changes().c).to.be.undefined;
});
it('orthogonalizes a single vertex in a quad path', function () {
// d --- c
// |
// a --- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2.1, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd']})
]);
var diff = iD.coreDifference(graph, iD.actionOrthogonalize('-', projection, 'b')(graph));
expect(diff.changes().a).to.be.undefined;
expect(diff.changes().b).to.be.not.undefined;
expect(diff.changes().c).to.be.undefined;
expect(diff.changes().d).to.be.undefined;
});
it('orthogonalizes a single vertex in a 3-point path', function () {
// a
// |
// |
// b - c
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 3]}),
iD.osmNode({id: 'b', loc: [0.1, 0]}),
iD.osmNode({id: 'c', loc: [3, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']})
]);
var diff = iD.coreDifference(graph, iD.actionOrthogonalize('-', projection, 'b')(graph));
expect(diff.changes().a).to.be.undefined;
expect(diff.changes().b).to.be.not.undefined;
expect(diff.changes().c).to.be.undefined;
});
});
describe('#disabled', function () {
describe('closed paths', function () {
it('returns "square_enough" for a perfect quad', function () {
// d ---- c
// | |
// a ---- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.eql('square_enough');
});
it('returns false for unsquared quad', function () {
// d --- c
// | |
// a ---- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2.1, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.be.false;
});
it('returns false for unsquared triangle', function () {
// a
// | \
// | \
// b - c
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 3]}),
iD.osmNode({id: 'b', loc: [0.1, 0]}),
iD.osmNode({id: 'c', loc: [3, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'a']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.be.false;
});
it('returns false for perfectly square shape with redundant nodes', function () {
// e - d - c
// | |
// a ----- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [1, 2]}),
iD.osmNode({id: 'e', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'a']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.be.false;
});
it('returns "not_squarish" for shape that can not be squared', function () {
// e -- d
// / \
// f c
// \ /
// a -- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [1, 0]}),
iD.osmNode({id: 'b', loc: [3, 0]}),
iD.osmNode({id: 'c', loc: [4, 2]}),
iD.osmNode({id: 'd', loc: [3, 4]}),
iD.osmNode({id: 'e', loc: [1, 4]}),
iD.osmNode({id: 'f', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.eql('not_squarish');
});
it('returns false for non-square self-intersecting shapes', function() {
// f -- g
// | |
// e --- d - c
// | |
// a -- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [ 0, -1]}),
iD.osmNode({id: 'b', loc: [ 1, -1]}),
iD.osmNode({id: 'c', loc: [ 0, 1]}),
iD.osmNode({id: 'd', loc: [ 0.1, 0]}),
iD.osmNode({id: 'e', loc: [-1, 0]}),
iD.osmNode({id: 'f', loc: [-1, 1]}),
iD.osmNode({id: 'g', loc: [ 0, 1]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'd', 'a']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.be.false;
});
});
describe('open paths', function () {
it('returns "square_enough" for a perfect quad', function () {
// d ---- c
// |
// a ---- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.eql('square_enough');
});
it('returns false for unsquared quad', function () {
// d --- c
// |
// a --- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2.1, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.be.false;
});
it('returns false for unsquared 3-point path', function () {
// a
// |
// |
// b - c
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 3]}),
iD.osmNode({id: 'b', loc: [0, 0.1]}),
iD.osmNode({id: 'c', loc: [3, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.be.false;
});
it('returns false for perfectly square shape with redundant nodes', function () {
// e - d - c
// |
// a ----- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [1, 2]}),
iD.osmNode({id: 'e', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.be.false;
});
it('returns "not_squarish" for path that can not be squared', function () {
// e -- d
// / \
// f c
// /
// a -- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [1, 0]}),
iD.osmNode({id: 'b', loc: [3, 0]}),
iD.osmNode({id: 'c', loc: [4, 2]}),
iD.osmNode({id: 'd', loc: [3, 4]}),
iD.osmNode({id: 'e', loc: [1, 4]}),
iD.osmNode({id: 'f', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.eql('not_squarish');
});
it('returns false for non-square self-intersecting paths', function() {
// f -- g
// | |
// e --- d - c
var graph = iD.coreGraph([
iD.osmNode({id: 'c', loc: [ 0, 1]}),
iD.osmNode({id: 'd', loc: [ 0.1, 0]}),
iD.osmNode({id: 'e', loc: [-1, 0]}),
iD.osmNode({id: 'f', loc: [-1, 1]}),
iD.osmNode({id: 'g', loc: [ 0, 1]}),
iD.osmWay({id: '-', nodes: ['c', 'd', 'e', 'f', 'g', 'd']})
]);
var result = iD.actionOrthogonalize('-', projection).disabled(graph);
expect(result).to.be.false;
});
});
describe('vertex-only', function () {
it('returns "square_enough" for a vertex in a perfect quad', function () {
// d ---- c
// |
// a ---- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd']})
]);
var result = iD.actionOrthogonalize('-', projection, 'b').disabled(graph);
expect(result).to.eql('square_enough');
});
it('returns false for a vertex in an unsquared quad', function () {
// d --- c
// |
// a --- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [2.1, 0]}),
iD.osmNode({id: 'c', loc: [2, 2]}),
iD.osmNode({id: 'd', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd']})
]);
var result = iD.actionOrthogonalize('-', projection, 'b').disabled(graph);
expect(result).to.be.false;
});
it('returns false for a vertex in an unsquared 3-point path', function () {
// a
// |
// |
// b - c
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 3]}),
iD.osmNode({id: 'b', loc: [0, 0.1]}),
iD.osmNode({id: 'c', loc: [3, 0]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c']})
]);
var result = iD.actionOrthogonalize('-', projection, 'b').disabled(graph);
expect(result).to.be.false;
});
it('returns "not_squarish" for vertex that can not be squared', function () {
// e -- d
// / \
// f c
// /
// a -- b
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [1, 0]}),
iD.osmNode({id: 'b', loc: [3, 0]}),
iD.osmNode({id: 'c', loc: [4, 2]}),
iD.osmNode({id: 'd', loc: [3, 4]}),
iD.osmNode({id: 'e', loc: [1, 4]}),
iD.osmNode({id: 'f', loc: [0, 2]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f']})
]);
var result = iD.actionOrthogonalize('-', projection, 'b').disabled(graph);
expect(result).to.eql('not_squarish');
});
});
});
describe('transitions', function () {
it('is transitionable', function() {
expect(iD.actionOrthogonalize().transitionable).to.be.true;
});
// for all of these:
//
// f ------------ e
// | |
// a -- b -- c -- d
it('orthogonalize at t = 0', function() {
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}),
iD.osmNode({id: 'c', loc: [2, -0.01]}),
iD.osmNode({id: 'd', loc: [3, 0]}),
iD.osmNode({id: 'e', loc: [3, 1]}),
iD.osmNode({id: 'f', loc: [0, 1]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']})
]);
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}),
iD.osmNode({id: 'c', loc: [2, -0.01]}),
iD.osmNode({id: 'd', loc: [3, 0]}),
iD.osmNode({id: 'e', loc: [3, 1]}),
iD.osmNode({id: 'f', loc: [0, 1]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph, 0);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c', 'd', 'e', 'f', 'a']);
@@ -145,14 +684,14 @@ describe('iD.actionOrthogonalize', function () {
it('orthogonalize at t = 0.5', function() {
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}),
iD.osmNode({id: 'c', loc: [2, -0.01]}),
iD.osmNode({id: 'd', loc: [3, 0]}),
iD.osmNode({id: 'e', loc: [3, 1]}),
iD.osmNode({id: 'f', loc: [0, 1]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']})
]);
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}),
iD.osmNode({id: 'c', loc: [2, -0.01]}),
iD.osmNode({id: 'd', loc: [3, 0]}),
iD.osmNode({id: 'e', loc: [3, 1]}),
iD.osmNode({id: 'f', loc: [0, 1]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph, 0.5);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c', 'd', 'e', 'f', 'a']);
@@ -164,14 +703,14 @@ describe('iD.actionOrthogonalize', function () {
it('orthogonalize at t = 1', function() {
var graph = iD.coreGraph([
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}),
iD.osmNode({id: 'c', loc: [2, -0.01]}),
iD.osmNode({id: 'd', loc: [3, 0]}),
iD.osmNode({id: 'e', loc: [3, 1]}),
iD.osmNode({id: 'f', loc: [0, 1]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']})
]);
iD.osmNode({id: 'a', loc: [0, 0]}),
iD.osmNode({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}),
iD.osmNode({id: 'c', loc: [2, -0.01]}),
iD.osmNode({id: 'd', loc: [3, 0]}),
iD.osmNode({id: 'e', loc: [3, 1]}),
iD.osmNode({id: 'f', loc: [0, 1]}),
iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']})
]);
graph = iD.actionOrthogonalize('-', projection)(graph, 1);
expect(graph.entity('-').nodes).to.eql(['a', 'b', 'd', 'e', 'f', 'a']);
+177 -177
View File
@@ -1,152 +1,152 @@
describe('iD.Difference', function () {
describe('iD.coreDifference', function () {
describe('#changes', function () {
it('includes created entities', function () {
var node = iD.osmNode({id: 'n'}),
base = iD.coreGraph(),
head = base.replace(node),
diff = iD.Difference(base, head);
var node = iD.osmNode({id: 'n'});
var base = iD.coreGraph();
var head = base.replace(node);
var diff = iD.coreDifference(base, head);
expect(diff.changes()).to.eql({n: {base: undefined, head: node}});
});
it('includes undone created entities', function () {
var node = iD.osmNode({id: 'n'}),
base = iD.coreGraph(),
head = base.replace(node),
diff = iD.Difference(head, base);
var node = iD.osmNode({id: 'n'});
var base = iD.coreGraph();
var head = base.replace(node);
var diff = iD.coreDifference(head, base);
expect(diff.changes()).to.eql({n: {base: node, head: undefined}});
});
it('includes modified entities', function () {
var n1 = iD.osmNode({id: 'n'}),
n2 = n1.update({ tags: { yes: 'no' } }),
base = iD.coreGraph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n'});
var n2 = n1.update({ tags: { yes: 'no' } });
var base = iD.coreGraph([n1]);
var head = base.replace(n2);
var diff = iD.coreDifference(base, head);
expect(diff.changes()).to.eql({n: {base: n1, head: n2}});
});
it('includes undone modified entities', function () {
var n1 = iD.osmNode({id: 'n'}),
n2 = n1.update({ tags: { yes: 'no' } }),
base = iD.coreGraph([n1]),
head = base.replace(n2),
diff = iD.Difference(head, base);
var n1 = iD.osmNode({id: 'n'});
var n2 = n1.update({ tags: { yes: 'no' } });
var base = iD.coreGraph([n1]);
var head = base.replace(n2);
var diff = iD.coreDifference(head, base);
expect(diff.changes()).to.eql({n: {base: n2, head: n1}});
});
it('doesn\'t include updated but identical entities', function () {
var n1 = iD.osmNode({id: 'n'}),
n2 = n1.update(),
base = iD.coreGraph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n'});
var n2 = n1.update();
var base = iD.coreGraph([n1]);
var head = base.replace(n2);
var diff = iD.coreDifference(base, head);
expect(diff.changes()).to.eql({});
});
it('includes deleted entities', function () {
var node = iD.osmNode({id: 'n'}),
base = iD.coreGraph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
var node = iD.osmNode({id: 'n'});
var base = iD.coreGraph([node]);
var head = base.remove(node);
var diff = iD.coreDifference(base, head);
expect(diff.changes()).to.eql({n: {base: node, head: undefined}});
});
it('includes undone deleted entities', function () {
var node = iD.osmNode({id: 'n'}),
base = iD.coreGraph([node]),
head = base.remove(node),
diff = iD.Difference(head, base);
var node = iD.osmNode({id: 'n'});
var base = iD.coreGraph([node]);
var head = base.remove(node);
var diff = iD.coreDifference(head, base);
expect(diff.changes()).to.eql({n: {base: undefined, head: node}});
});
it('doesn\'t include created entities that were subsequently deleted', function () {
var node = iD.osmNode(),
base = iD.coreGraph(),
head = base.replace(node).remove(node),
diff = iD.Difference(base, head);
var node = iD.osmNode();
var base = iD.coreGraph();
var head = base.replace(node).remove(node);
var diff = iD.coreDifference(base, head);
expect(diff.changes()).to.eql({});
});
it('doesn\'t include created entities that were subsequently reverted', function () {
var node = iD.osmNode({id: 'n-1'}),
base = iD.coreGraph(),
head = base.replace(node).revert('n-1'),
diff = iD.Difference(base, head);
var node = iD.osmNode({id: 'n-1'});
var base = iD.coreGraph();
var head = base.replace(node).revert('n-1');
var diff = iD.coreDifference(base, head);
expect(diff.changes()).to.eql({});
});
it('doesn\'t include modified entities that were subsequently reverted', function () {
var n1 = iD.osmNode({id: 'n'}),
n2 = n1.update({ tags: { yes: 'no' } }),
base = iD.coreGraph([n1]),
head = base.replace(n2).revert('n'),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n'});
var n2 = n1.update({ tags: { yes: 'no' } });
var base = iD.coreGraph([n1]);
var head = base.replace(n2).revert('n');
var diff = iD.coreDifference(base, head);
expect(diff.changes()).to.eql({});
});
it('doesn\'t include deleted entities that were subsequently reverted', function () {
var node = iD.osmNode({id: 'n'}),
base = iD.coreGraph([node]),
head = base.remove(node).revert('n'),
diff = iD.Difference(base, head);
var node = iD.osmNode({id: 'n'});
var base = iD.coreGraph([node]);
var head = base.remove(node).revert('n');
var diff = iD.coreDifference(base, head);
expect(diff.changes()).to.eql({});
});
});
describe('#extantIDs', function () {
it('includes the ids of created entities', function () {
var node = iD.osmNode({id: 'n'}),
base = iD.coreGraph(),
head = base.replace(node),
diff = iD.Difference(base, head);
var node = iD.osmNode({id: 'n'});
var base = iD.coreGraph();
var head = base.replace(node);
var diff = iD.coreDifference(base, head);
expect(diff.extantIDs()).to.eql(['n']);
});
it('includes the ids of modified entities', function () {
var n1 = iD.osmNode({id: 'n'}),
n2 = n1.move([1, 2]),
base = iD.coreGraph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n'});
var n2 = n1.move([1, 2]);
var base = iD.coreGraph([n1]);
var head = base.replace(n2);
var diff = iD.coreDifference(base, head);
expect(diff.extantIDs()).to.eql(['n']);
});
it('omits the ids of deleted entities', function () {
var node = iD.osmNode({id: 'n'}),
base = iD.coreGraph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
var node = iD.osmNode({id: 'n'});
var base = iD.coreGraph([node]);
var head = base.remove(node);
var diff = iD.coreDifference(base, head);
expect(diff.extantIDs()).to.eql([]);
});
});
describe('#created', function () {
it('returns an array of created entities', function () {
var node = iD.osmNode({id: 'n'}),
base = iD.coreGraph(),
head = base.replace(node),
diff = iD.Difference(base, head);
var node = iD.osmNode({id: 'n'});
var base = iD.coreGraph();
var head = base.replace(node);
var diff = iD.coreDifference(base, head);
expect(diff.created()).to.eql([node]);
});
});
describe('#modified', function () {
it('returns an array of modified entities', function () {
var n1 = iD.osmNode({id: 'n'}),
n2 = n1.move([1, 2]),
base = iD.coreGraph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n'});
var n2 = n1.move([1, 2]);
var base = iD.coreGraph([n1]);
var head = base.replace(n2);
var diff = iD.coreDifference(base, head);
expect(diff.modified()).to.eql([n2]);
});
});
describe('#deleted', function () {
it('returns an array of deleted entities', function () {
var node = iD.osmNode({id: 'n'}),
base = iD.coreGraph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
var node = iD.osmNode({id: 'n'});
var base = iD.coreGraph([node]);
var head = base.remove(node);
var diff = iD.coreDifference(base, head);
expect(diff.deleted()).to.eql([node]);
});
});
@@ -160,9 +160,9 @@ describe('iD.Difference', function () {
]);
it('reports a created way as created', function() {
var way = iD.osmWay({id: '+'}),
head = base.replace(way),
diff = iD.Difference(base, head);
var way = iD.osmWay({id: '+'});
var head = base.replace(way);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'created',
@@ -172,9 +172,9 @@ describe('iD.Difference', function () {
});
it('reports a deleted way as deleted', function() {
var way = base.entity('-'),
head = base.remove(way),
diff = iD.Difference(base, head);
var way = base.entity('-');
var head = base.remove(way);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'deleted',
@@ -184,9 +184,9 @@ describe('iD.Difference', function () {
});
it('reports a modified way as modified', function() {
var way = base.entity('-').mergeTags({highway: 'primary'}),
head = base.replace(way),
diff = iD.Difference(base, head);
var way = base.entity('-').mergeTags({highway: 'primary'});
var head = base.replace(way);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'modified',
@@ -196,9 +196,9 @@ describe('iD.Difference', function () {
});
it('reports a way as modified when a member vertex is moved', function() {
var vertex = base.entity('b').move([0,3]),
head = base.replace(vertex),
diff = iD.Difference(base, head);
var vertex = base.entity('b').move([0,3]);
var head = base.replace(vertex);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'modified',
@@ -208,10 +208,10 @@ describe('iD.Difference', function () {
});
it('reports a way as modified when a member vertex is added', function() {
var vertex = iD.osmNode({id: 'c'}),
way = base.entity('-').addNode('c'),
head = base.replace(vertex).replace(way),
diff = iD.Difference(base, head);
var vertex = iD.osmNode({id: 'c'});
var way = base.entity('-').addNode('c');
var head = base.replace(vertex).replace(way);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'modified',
@@ -221,9 +221,9 @@ describe('iD.Difference', function () {
});
it('reports a way as modified when a member vertex is removed', function() {
var way = base.entity('-').removeNode('b'),
head = base.replace(way),
diff = iD.Difference(base, head);
var way = base.entity('-').removeNode('b');
var head = base.replace(way);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'modified',
@@ -233,10 +233,10 @@ describe('iD.Difference', function () {
});
it('reports a created way containing a moved vertex as being created', function() {
var vertex = base.entity('b').move([0,3]),
way = iD.osmWay({id: '+', nodes: ['b']}),
head = base.replace(way).replace(vertex),
diff = iD.Difference(base, head);
var vertex = base.entity('b').move([0,3]);
var way = iD.osmWay({id: '+', nodes: ['b']});
var head = base.replace(way).replace(vertex);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'created',
@@ -250,10 +250,10 @@ describe('iD.Difference', function () {
});
it('reports a created way with a created vertex as being created', function() {
var vertex = iD.osmNode({id: 'c'}),
way = iD.osmWay({id: '+', nodes: ['c']}),
head = base.replace(vertex).replace(way),
diff = iD.Difference(base, head);
var vertex = iD.osmNode({id: 'c'});
var way = iD.osmWay({id: '+', nodes: ['c']});
var head = base.replace(vertex).replace(way);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'created',
@@ -263,9 +263,9 @@ describe('iD.Difference', function () {
});
it('reports a vertex as modified when it has tags and they are changed', function() {
var vertex = base.entity('a').mergeTags({highway: 'traffic_signals'}),
head = base.replace(vertex),
diff = iD.Difference(base, head);
var vertex = base.entity('a').mergeTags({highway: 'traffic_signals'});
var head = base.replace(vertex);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'modified',
@@ -275,9 +275,9 @@ describe('iD.Difference', function () {
});
it('reports a vertex as modified when it has tags and is moved', function() {
var vertex = base.entity('a').move([1, 2]),
head = base.replace(vertex),
diff = iD.Difference(base, head);
var vertex = base.entity('a').move([1, 2]);
var head = base.replace(vertex);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'modified',
@@ -291,9 +291,9 @@ describe('iD.Difference', function () {
});
it('does not report a vertex as modified when it is moved and has no-op tag changes', function() {
var vertex = base.entity('b').update({tags: {}, loc: [1, 2]}),
head = base.replace(vertex),
diff = iD.Difference(base, head);
var vertex = base.entity('b').update({tags: {}, loc: [1, 2]});
var head = base.replace(vertex);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'modified',
@@ -303,9 +303,9 @@ describe('iD.Difference', function () {
});
it('reports a vertex as deleted when it had tags', function() {
var vertex = base.entity('v'),
head = base.remove(vertex),
diff = iD.Difference(base, head);
var vertex = base.entity('v');
var head = base.remove(vertex);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'deleted',
@@ -315,10 +315,10 @@ describe('iD.Difference', function () {
});
it('reports a vertex as created when it has tags', function() {
var vertex = iD.osmNode({id: 'c', tags: {crossing: 'marked'}}),
way = base.entity('-').addNode('c'),
head = base.replace(way).replace(vertex),
diff = iD.Difference(base, head);
var vertex = iD.osmNode({id: 'c', tags: {crossing: 'marked'}});
var way = base.entity('-').addNode('c');
var head = base.replace(way).replace(vertex);
var diff = iD.coreDifference(base, head);
expect(diff.summary()).to.eql([{
changeType: 'modified',
@@ -334,107 +334,107 @@ describe('iD.Difference', function () {
describe('#complete', function () {
it('includes created entities', function () {
var node = iD.osmNode({id: 'n'}),
base = iD.coreGraph(),
head = base.replace(node),
diff = iD.Difference(base, head);
var node = iD.osmNode({id: 'n'});
var base = iD.coreGraph();
var head = base.replace(node);
var diff = iD.coreDifference(base, head);
expect(diff.complete().n).to.equal(node);
});
it('includes modified entities', function () {
var n1 = iD.osmNode({id: 'n'}),
n2 = n1.move([1, 2]),
base = iD.coreGraph([n1]),
head = base.replace(n2),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n'});
var n2 = n1.move([1, 2]);
var base = iD.coreGraph([n1]);
var head = base.replace(n2);
var diff = iD.coreDifference(base, head);
expect(diff.complete().n).to.equal(n2);
});
it('includes deleted entities', function () {
var node = iD.osmNode({id: 'n'}),
base = iD.coreGraph([node]),
head = base.remove(node),
diff = iD.Difference(base, head);
var node = iD.osmNode({id: 'n'});
var base = iD.coreGraph([node]);
var head = base.remove(node);
var diff = iD.coreDifference(base, head);
expect(diff.complete()).to.eql({n: undefined});
});
it('includes nodes added to a way', function () {
var n1 = iD.osmNode({id: 'n1'}),
n2 = iD.osmNode({id: 'n2'}),
w1 = iD.osmWay({id: 'w', nodes: ['n1']}),
w2 = w1.addNode('n2'),
base = iD.coreGraph([n1, n2, w1]),
head = base.replace(w2),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n1'});
var n2 = iD.osmNode({id: 'n2'});
var w1 = iD.osmWay({id: 'w', nodes: ['n1']});
var w2 = w1.addNode('n2');
var base = iD.coreGraph([n1, n2, w1]);
var head = base.replace(w2);
var diff = iD.coreDifference(base, head);
expect(diff.complete().n2).to.equal(n2);
});
it('includes nodes removed from a way', function () {
var n1 = iD.osmNode({id: 'n1'}),
n2 = iD.osmNode({id: 'n2'}),
w1 = iD.osmWay({id: 'w', nodes: ['n1', 'n2']}),
w2 = w1.removeNode('n2'),
base = iD.coreGraph([n1, n2, w1]),
head = base.replace(w2),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n1'});
var n2 = iD.osmNode({id: 'n2'});
var w1 = iD.osmWay({id: 'w', nodes: ['n1', 'n2']});
var w2 = w1.removeNode('n2');
var base = iD.coreGraph([n1, n2, w1]);
var head = base.replace(w2);
var diff = iD.coreDifference(base, head);
expect(diff.complete().n2).to.equal(n2);
});
it('includes parent ways of modified nodes', function () {
var n1 = iD.osmNode({id: 'n'}),
n2 = n1.move([1, 2]),
way = iD.osmWay({id: 'w', nodes: ['n']}),
base = iD.coreGraph([n1, way]),
head = base.replace(n2),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n'});
var n2 = n1.move([1, 2]);
var way = iD.osmWay({id: 'w', nodes: ['n']});
var base = iD.coreGraph([n1, way]);
var head = base.replace(n2);
var diff = iD.coreDifference(base, head);
expect(diff.complete().w).to.equal(way);
});
it('includes parent relations of modified entities', function () {
var n1 = iD.osmNode({id: 'n'}),
n2 = n1.move([1, 2]),
rel = iD.osmRelation({id: 'r', members: [{id: 'n'}]}),
base = iD.coreGraph([n1, rel]),
head = base.replace(n2),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n'});
var n2 = n1.move([1, 2]);
var rel = iD.osmRelation({id: 'r', members: [{id: 'n'}]});
var base = iD.coreGraph([n1, rel]);
var head = base.replace(n2);
var diff = iD.coreDifference(base, head);
expect(diff.complete().r).to.equal(rel);
});
it('includes parent relations of modified entities, recursively', function () {
var n1 = iD.osmNode({id: 'n'}),
n2 = n1.move([1, 2]),
rel1 = iD.osmRelation({id: 'r1', members: [{id: 'n'}]}),
rel2 = iD.osmRelation({id: 'r2', members: [{id: 'r1'}]}),
base = iD.coreGraph([n1, rel1, rel2]),
head = base.replace(n2),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n'});
var n2 = n1.move([1, 2]);
var rel1 = iD.osmRelation({id: 'r1', members: [{id: 'n'}]});
var rel2 = iD.osmRelation({id: 'r2', members: [{id: 'r1'}]});
var base = iD.coreGraph([n1, rel1, rel2]);
var head = base.replace(n2);
var diff = iD.coreDifference(base, head);
expect(diff.complete().r2).to.equal(rel2);
});
it('includes parent relations of parent ways of modified nodes', function () {
var n1 = iD.osmNode({id: 'n'}),
n2 = n1.move([1, 2]),
way = iD.osmWay({id: 'w', nodes: ['n']}),
rel = iD.osmRelation({id: 'r', members: [{id: 'w'}]}),
base = iD.coreGraph([n1, way, rel]),
head = base.replace(n2),
diff = iD.Difference(base, head);
var n1 = iD.osmNode({id: 'n'});
var n2 = n1.move([1, 2]);
var way = iD.osmWay({id: 'w', nodes: ['n']});
var rel = iD.osmRelation({id: 'r', members: [{id: 'w'}]});
var base = iD.coreGraph([n1, way, rel]);
var head = base.replace(n2);
var diff = iD.coreDifference(base, head);
expect(diff.complete().r).to.equal(rel);
});
it('copes with recursive relations', function () {
var node = iD.osmNode({id: 'n'}),
rel1 = iD.osmRelation({id: 'r1', members: [{id: 'n'}, {id: 'r2'}]}),
rel2 = iD.osmRelation({id: 'r2', members: [{id: 'r1'}]}),
base = iD.coreGraph([node, rel1, rel2]),
head = base.replace(node.move([1, 2])),
diff = iD.Difference(base, head);
var node = iD.osmNode({id: 'n'});
var rel1 = iD.osmRelation({id: 'r1', members: [{id: 'n'}, {id: 'r2'}]});
var rel2 = iD.osmRelation({id: 'r2', members: [{id: 'r1'}]});
var base = iD.coreGraph([node, rel1, rel2]);
var head = base.replace(node.move([1, 2]));
var diff = iD.coreDifference(base, head);
expect(diff.complete()).to.be.ok;
});
+74
View File
@@ -76,6 +76,17 @@ describe('iD.geo - vector', function() {
});
});
describe('geoVecNormalize', function() {
it('gets unit vectors', function() {
expect(iD.geoVecNormalize([0, 0])).to.eql([0, 0]);
expect(iD.geoVecNormalize([1, 0])).to.eql([1, 0]);
expect(iD.geoVecNormalize([5, 0])).to.eql([1, 0]);
expect(iD.geoVecNormalize([-5, 0])).to.eql([-1, 0]);
expect(iD.geoVecNormalize([1, 1])[0]).to.be.closeTo(Math.sqrt(2)/2, 1e-6);
expect(iD.geoVecNormalize([1, 1])[1]).to.be.closeTo(Math.sqrt(2)/2, 1e-6);
});
});
describe('geoVecAngle', function() {
it('returns angle between a and b', function() {
expect(iD.geoVecAngle([0, 0], [1, 0])).to.be.closeTo(0, 1e-6);
@@ -98,6 +109,24 @@ describe('iD.geo - vector', function() {
});
});
describe('geoVecNormalizedDot', function() {
it('normalized dot product of right angle is zero', function() {
var a = [2, 0];
var b = [0, 2];
expect(iD.geoVecNormalizedDot(a, b)).to.eql(0);
});
it('normalized dot product of same vector multiplies unit vectors', function() {
var a = [2, 0];
var b = [2, 0];
expect(iD.geoVecNormalizedDot(a, b)).to.eql(1);
});
it('normalized dot product of 45 degrees', function() {
var a = [0, 2];
var b = [2, 2];
expect(iD.geoVecNormalizedDot(a, b)).to.be.closeTo(Math.sqrt(2)/2, 1e-6);
});
});
describe('geoVecCross', function() {
it('2D cross product of right hand turn is positive', function() {
var a = [2, 0];
@@ -116,4 +145,49 @@ describe('iD.geo - vector', function() {
});
});
describe('geoVecProject', function() {
it('returns null for a degenerate path (no nodes)', function() {
expect(iD.geoVecProject([0, 1], [])).to.be.null;
});
it('returns null for a degenerate path (single node)', function() {
expect(iD.geoVecProject([0, 1], [0, 0])).to.be.null;
});
it('calculates the orthogonal projection of a point onto a path', function() {
// c
// |
// a --*--- b
//
// * = [2, 0]
var a = [0, 0];
var b = [5, 0];
var c = [2, 1];
var choice = iD.geoVecProject(c, [a, b]);
expect(choice.index).to.eql(1);
expect(choice.distance).to.eql(1);
expect(choice.target).to.eql([2, 0]);
});
it('returns the starting vertex when the orthogonal projection is < 0', function() {
var a = [0, 0];
var b = [5, 0];
var c = [-3, 4];
var choice = iD.geoVecProject(c, [a, b]);
expect(choice.index).to.eql(1);
expect(choice.distance).to.eql(5);
expect(choice.target).to.eql([0, 0]);
});
it('returns the ending vertex when the orthogonal projection is > 1', function() {
var a = [0, 0];
var b = [5, 0];
var c = [8, 4];
var choice = iD.geoVecProject(c, [a, b]);
expect(choice.index).to.eql(1);
expect(choice.distance).to.eql(5);
expect(choice.target).to.eql([5, 0]);
});
});
});