modules/actions/rapid_accept_feature.js (129 lines of code) (raw):
import { vecInterp } from '@id-sdk/math';
import { osmNode, osmRelation, osmWay } from '../osm';
function findConnectionPoint(graph, newNode, targetWay, nodeA, nodeB) {
// Find the place to newNode on targetWay between nodeA and nodeB if it does
// not alter the existing segment's angle much. There may be other nodes
// between A and B from user edit or other automatic connections.
var sortByLon = Math.abs(nodeA.loc[0] - nodeB.loc[0]) > Math.abs(nodeA.loc[1] - nodeB.loc[1]);
var sortFunc = sortByLon
? function(n1, n2) {
return nodeA.loc[0] < nodeB.loc[0]
? n1.loc[0] - n2.loc[0]
: n2.loc[0] - n1.loc[0];
}
: function(n1, n2) {
return nodeA.loc[1] < nodeB.loc[1]
? n1.loc[1] - n2.loc[1]
: n2.loc[1] - n1.loc[1];
};
var nidList = targetWay.nodes;
var idxA = nidList.indexOf(nodeA.id);
var idxB = nidList.indexOf(nodeB.id);
// Invariants for finding the insert index below: A and B must be in the
// node list, in order, and the sort function must also order A before B
if (idxA === -1 || idxB === -1 || idxA >= idxB || sortFunc(nodeA, nodeB) >= 0) {
return null;
}
var insertIdx = idxA + 1; // index to insert immediately before
while (insertIdx < idxB && sortFunc(newNode, graph.entity(nidList[insertIdx])) > 0) {
insertIdx++;
}
// Find the interpolated point on the segment where insertion will not
// alter the segment's angle.
var locA = graph.entity(nidList[insertIdx - 1]).loc;
var locB = graph.entity(nidList[insertIdx]).loc;
var locN = newNode.loc;
var coeff = Math.abs(locA[0] - locB[0]) > Math.abs(locA[1] - locB[1])
? (locN[0] - locA[0]) / (locB[0] - locA[0])
: (locN[1] - locA[1]) / (locB[1] - locA[1]);
var interpLoc = vecInterp(locA, locB, coeff);
return {
insertIdx: insertIdx,
interpLoc: interpLoc,
};
}
function locationChanged(loc1, loc2) {
return Math.abs(loc1[0] - loc2[0]) > 2e-5
|| Math.abs(loc1[1] - loc2[1]) > 2e-5;
}
function removeMetadata(entity) {
delete entity.__fbid__;
delete entity.__origid__;
delete entity.__service__;
delete entity.__datasetid__;
delete entity.tags.conn;
delete entity.tags.dupe;
}
export function actionRapidAcceptFeature(entityID, extGraph) {
return function(graph) {
var seenRelations = {}; // keep track of seen relations to avoid infinite recursion
var extEntity = extGraph.entity(entityID);
if (extEntity.type === 'node') {
acceptNode(extEntity);
} else if (extEntity.type === 'way') {
acceptWay(extEntity);
} else if (extEntity.type === 'relation') {
acceptRelation(extEntity);
}
return graph;
// These functions each accept the external entities, returning the replacement
// NOTE - these functions will update `graph` closure variable
function acceptNode(extNode) {
// copy node before modifying
var node = osmNode(extNode);
node.tags = Object.assign({}, node.tags);
removeMetadata(node);
graph = graph.replace(node);
return node;
}
function acceptWay(extWay) {
// copy way before modifying
var way = osmWay(extWay);
way.nodes = extWay.nodes.slice();
way.tags = Object.assign({}, way.tags);
removeMetadata(way);
var nodes = way.nodes.map(function(nodeId) {
// copy node before modifying
var node = osmNode(extGraph.entity(nodeId));
node.tags = Object.assign({}, node.tags);
var conn = node.tags.conn && node.tags.conn.split(',');
var dupeId = node.tags.dupe;
removeMetadata(node);
if (dupeId && graph.hasEntity(dupeId) && !locationChanged(graph.entity(dupeId).loc, node.loc)) {
node = graph.entity(dupeId); // keep original node with dupeId
} else if (graph.hasEntity(node.id) && locationChanged(graph.entity(node.id).loc, node.loc)) {
node = osmNode({ loc: node.loc }); // replace (unnecessary copy of node?)
}
if (conn && graph.hasEntity(conn[0])) {
//conn=w316746574,n3229071295,n3229071273
var targetWay = graph.entities[conn[0]];
var nodeA = graph.entities[conn[1]];
var nodeB = graph.entities[conn[2]];
if (targetWay && nodeA && nodeB) {
var result = findConnectionPoint(graph, node, targetWay, nodeA, nodeB);
if (result && !locationChanged(result.interpLoc, node.loc)) {
node.loc = result.interpLoc;
graph = graph.replace(targetWay.addNode(node.id, result.insertIdx));
}
}
}
graph = graph.replace(node);
return node.id;
});
way = way.update({ nodes: nodes });
graph = graph.replace(way);
return way;
}
function acceptRelation(extRelation) {
var seen = seenRelations[extRelation.id];
if (seen) return seen;
// copy relation before modifying
var relation = osmRelation(extRelation);
relation.members = extRelation.members.slice();
relation.tags = Object.assign({}, extRelation.tags);
removeMetadata(relation);
var members = relation.members.map(function(member) {
var extEntity = extGraph.entity(member.id);
var replacement;
if (extEntity.type === 'node') {
replacement = acceptNode(extEntity);
} else if (extEntity.type === 'way') {
replacement = acceptWay(extEntity);
} else if (extEntity.type === 'relation') {
replacement = acceptRelation(extEntity);
}
return Object.assign(member, { id: replacement.id });
});
relation = relation.update({ members: members });
graph = graph.replace(relation);
seenRelations[extRelation.id] = relation;
return relation;
}
};
}